1. Codeforces Round #783 (Div. 2)D. Optimal Partition
https://codeforces.ml/contest/1668/problem/D
题目:划分长为n的序列a,设s是a[l]到a[r]闭区间的和;
a[l]到a[r]这串子序列的价值如下:
- (r−l+1)if s>0,
- 0 if s=0,
- −(r−l+1) if s<0.
求能划分出来的最大价值。
动态规划部分
首先可以看到,为了价值最大,每个a[i]为负的都划成单独的块,即它的价值为-1,设s是前缀和,那么可能:
dp[i] = dp[i-1]-1 条件:a[i]<0
dp[i] = max( dp[i] , dp[i-1] ) 条件:a[i]==0(因为有条件1,所以不会出现一段总和为0的情况)
dp[i] = max( dp[i] , dp[i-1]+1 ) 条件:a[i]>0
dp[i]=max ( dp[j]+(i-j) , dp[i] ) a[j+1]+a[j+2]+……+a[i]>0等价于s[i]-s[j]
题目中:a[j]+……+a[i]>0也就是s[i]-s[j-1]>0,dp[i]=max ( dp[j]+( i-j+1) , dp[i] ) ,和上面是一样的,也就是把j-1(j从1开始)看成j (j从0开始,可以在j<=i-2)
注意:前三条是一部分,最后一条是一部分,两部分都要做。当然全合在一起用s判断好像也没什么问题。【没试过】
再次注意下面的代码要从j=0开始写!
for(int j=0;j<i;j++) if(sum[i]-sum[j]>0)dp[i]=max(dp[i],dp[j]+(i-j));
做的时候初始话未负无穷,因为只有从0才能开始,如果不初始化为负无穷,只要是0都可以作为开始的起点。
时间复杂度为O(n2)
#include<bits/stdc++.h>
using namespace std;
int a[500005];
int sum[500005];
int dp[500005];
int main()
{
int T;cin>>T;
while(T--){
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];sum[i]=sum[i-1]+a[i];dp[i]=-0x3f3f3f3f;
}
dp[0]=0;
for(int i=1;i<=n;i++){
if(a[i]<0)dp[i]=max(dp[i],dp[i-1]-1);
else if(a[i]==0)dp[i]=max(dp[i],dp[i-1]);
else dp[i]=max(dp[i],dp[i-1]+1);
for(int j=0;j<i;j++){
if(sum[i]-sum[j]>0)dp[i]=max(dp[i],dp[j]+(i-j));
}
}
cout<<dp[n]<<endl;
}
return 0;
}
但是,n的范围有5e5,所以O(n2)不行。
于是考虑优化dp[i]=max ( dp[j]+(i-j) , dp[i] ) ,也就是判断 dp[i]-i 和 dp[j]-j 那个更大。
树状数组部分
树状数组简介:树状数组 bestsort的博客-CSDN博客_树状数组
int lowbit(x){return x&(-x);}
void update(int x,int y,int n){//单点更新
for(int i=x;i<=n;i+=lowbit(i)) c[i] += y;
for(int i=x;i<=n;i+=lowbit(i)) c[i]=max(y,c[i]);//求最大值
}
int getsum(int x){
int ans = 0;
for(int i=x;i;i-=lowbit(i)) ans+=c[i];//下标1到x的区间求和
for(int i=x;i;i-=lowbit(i)) ans=max(ans,c[i]);//下标1到x的区间求最大值
return ans;
}
于是从[0,i]这个区间里求最大值就能求出来了,但还有一个前提条件(sum[i]-sum[j]>0)需要解决
这就需要离散化了。
首先把所有的sum数组放在一个set里,set会自动把值排序,把set中的这些值排序,这样就能得到sum中的某个值是第几大了。也就能解决sum[i] 在比自己小的sum[j]中 寻找dp[j]-j的最大值。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=0x3f3f3f3f;
int a[500005];
int sum[500005];
int dp[500005];
int c[500005];
map<int,int>mp;
set<int>s;
int lowbit(int x){return x&(-x);}
void update(int x,int y,int n){//单点更新
for(int i=x;i<=n;i+=lowbit(i)) c[i]=max(y,c[i]);//求最大值
}
int getmax(int x){
int ans =-inf;
for(int i=x;i;i-=lowbit(i)) ans=max(ans,c[i]);//下标1到x的区间求最大值
return ans;
}
signed main()
{
int T;cin>>T;
while(T--){
int n;cin>>n;s.clear();mp.clear();
s.insert(0);//初始
for(int i=1;i<=n;i++){
cin>>a[i];sum[i]=sum[i-1]+a[i];dp[i]=-inf;c[i]=-inf;s.insert(sum[i]);
}
int cnt=1;
for (auto i:s){mp[i]=cnt++;}//离散化标号,树状数组从1开始
//c[0]不一定为0,因为sum可能还有负数的
update(mp[sum[0]],0,n);//mp[sum[0]]=0表示dp[0]-0=0,代表了dp[0]=0
for(int i=1;i<=n;i++){
if(a[i]<0)dp[i]=max(dp[i],dp[i-1]-1);
else if(a[i]==0)dp[i]=max(dp[i],dp[i-1]);
else dp[i]=max(dp[i],dp[i-1]+1);
update(mp[sum[i]],dp[i]-i,n);
dp[i]=max(dp[i],getmax(mp[sum[i]]-1)+i);//不要和i自己比较
update(mp[sum[i]],dp[i]-i,n);
}
cout<<dp[n]<<endl;
}
return 0;
}