題意:
有一個長度為n的序列a,和初始分數0,一個整數k,分數會從左到右依據序列的值進行變化,變化的規則如下:
如果此時分數為x
分數沒有達到過k: x=x+a[i]
分數達到過k: x=min(x+a[i],k)
給你序列a和初始分數0,你需要找到一個k使得最後的分數最大
樣例:
第一組
n:4
a:3 -2 1 2
答案: 6
k=3,分數變化: 0 -> 3 -> 3 -> 4 -> 6
第二組:
n:7
a:5 1 -3 2 -1 -2 2
答案: 8
k=6,分數變化: 0 -> 5 -> 6 -> 6 -> 8 -> 7 -> 6 ->8
分析:
就樣例2進行分析
k=6時,
首先我們需要找到達到k的第一個點(我們稱這個點為分界點),所以我們需要得到a的字首和陣列s:
5 6 3 5 4 2 4
在i=2時k>=6,所以在i=2之後的點每小於6就加到6,後面的數也加上相同的差值,這樣一直到s[n]
就是在k=6時的答案
變化過程:
5 6 3 5 4 2 4
5 6 6 8 7 5 7
5 6 6 8 7 6 8
由上面的分析可知,要使最後的s[n]最大,就是要使加上的值總和最大
那麼該如何求這個加上的值總和的最大值?
假設在分界點之後,所有小於k的點的序列為b
如果b[i]<b[j],那麼在b[i]加到k時,b[j]必然是大於k的,那麼b[j]就沒有存在的必要了
那麼把所有滿足條件的b[j]刪掉,就可以得到新的序列b,長度為m,那麼這個序列必然是單調遞減的
b[1] b[2] b[3].....b[m-1] b[m]
那麼加上的值的綜合就等於
(k-b[1])+(k-(b[2]+(k-b[1])))+........ = k-b[m]
那麼最大的k-b[m]就很好求了,對於每個可能的分界點,我們需要找到它最大的k以及最小的b[m];
因為分界點的值>=k,所以最大的k就是分界點本身的值
而最小的b[m]就是分界點之後的最小值
最後的答案就是max(k-b[m])+s[n]
什麼樣的點可能是分界點?
成為分界點的條件就是大於前面所有的點
時間複雜度o(n)
程式碼:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N =3e5+5;
#define int long long
int t,n,mi[N],a[N];
signed main(){
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)a[i]+=a[i-1];
mi[n+1]=1e18+5;
for(int i=n;i>=0;i--)mi[i]=min(mi[i+1],a[i]);
int mx=-1e18-1,res=0,resi;
for(int i=0;i<=n;i++){
if(a[i]>mx){
mx=a[i];
if(mx-mi[i+1]>res){
res=mx-mi[i+1];
resi=i;
}
}
}
cout<<a[resi]<<endl;
}
return 0;
}