220501学习记录 动态规划 树状数组 离散化

本文解析了一道Codeforces竞赛题目,通过动态规划和树状数组优化求解最优序列划分问题。介绍了如何处理负数子序列的价值计算,以及如何利用离散化和树状数组加速最大价值的查找。关键在于理解区间和的比较和dp状态转移的优化。
摘要由CSDN通过智能技术生成

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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值