当前位置: 代码迷 >> 综合 >> Codeforces Round #783 (Div. 2) D. Optimal Partition
  详细解决方案

Codeforces Round #783 (Div. 2) D. Optimal Partition

热度:71   发布时间:2023-12-03 16:46:55.0

题意:

给定一个由 n 个整数组成的数组 a。 您应该将 a 划分为连续的非空子数组(有 2n-1 种方法可以做到这一点)。

令 s=al+al+1+…+ar。 子数组 al,al+1,…,ar 的值为:

(r?l+1) 如果 s>0,
0 如果 s=0,
-(r-l+1) 如果 s<0。
使用分区可以获得的最大值总和是多少?
输入
输入由多个测试用例组成。 第一行包含一个整数 t (1≤t≤5?105) — 测试用例的数量。 测试用例的描述如下。

每个测试用例的第一行包含一个整数 n (1≤n≤5?105)。

每个测试用例的第二行包含 n 个整数 a1, a2, ..., an (?109≤ai≤109)。

保证所有测试用例的 n 总和不超过 5?105。

输出
为每个测试用例打印一个整数——使用最佳分区可以获得的最大值总和。

想法:

我们可以发现对于这题,我们可以迅速建立dp方程,即dp[i]=max(dp[i],dp[j]+sga(i-j)),为什么是r-l呢,因为我们的方程是寻求第j个前面的情况和j+1,j+2,j+3......等等到r的,所以这些数量合起来只有i-j个。然而我们发现这个方法如果要暴力更新的话,方法是n2的,明显超出了我们想要的时间复杂度。那么我们可以有两种想法:

一种是用三个树状数组分别维护dp[j]-j,fp[j]+j,dp[j],然后分别更新当前的最大值dp[i]变成树状数组1+i,树状数组2-i,树状数组3的最大值,然后一直更新最大值,然而我还没写这种方法。

第二种方法就是我们只需要维护一个dp[j]-j的树状数组就可以了,因为我们发现如果是负数的话,拆开区间一定不可能比总共都是负数的情况更劣,如果是0的话,会拆成全是0的或者一正一负的,这种方法也不可能会比每次通过正数更新的方法更优,因此我们也不需要考虑0的情况,因此我们只要考虑从i到j的这个区间是正数的情况就可以了。

因为我们要做到这一点,所以我们开始要把树状数组的最大值全部初始化为-inf,为什么呢,因为这个-inf的话,可以让一开始的还没加入sum[i]的情况不能进行更新。我们采用树状数组是什么原理呢,我们开始只要离散化掉这个前缀和sum[i],然后对其进行插入操作,也就是把这个sum[i]根据大小关系插入到他该属于的相应位置就行了,为什么可以离散化掉呢,因为我们只要考虑sum[i]的相对应的大小关系,所以我们不用管前缀和的值而将它离散化掉。离散化掉的前缀和我们用来做什么呢,我们用来表示相对应的大小,维护出dp[j]-j的最大值出来,只要我们能找出当前的sum[i]-以前的sum[j]还能大于0的j出来,我们才可以按照区间>0进行更新,可能是本萌新太笨了吧,很多大佬的题解中都没表示出为什么要离散化的原因和为什么要树状数组的原因。然后为什么要维护最大呢,很简单,因为当区间是正数的时候,我们的公式是

dp[i]=dp[j]+(i-j)。我们可以发现dp[j]和j这个是过去就已经需要准备好的用来更新的了,那我们就知道了我们每一次dp[i]更新结束后都应该将它插入sum[i]离散后的哪个位置了,然后我们的dp[j]-j每一次都要找sum[i]-1的位置开始寻找最大值,因为我们要让这个区间>0。然后我们每一次都要试试周边能不能进行更新来顶替负数区间和0的操作,因为如果没有区间大于0的操作的时候,拆开就是最好的选择.

所以代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
const int Maxn = 5e5+10;
const int INF=1e18;
int a[Maxn],b[Maxn],c[Maxn],h[Maxn],sum[Maxn],dp[Maxn],n;  // a[] 数列的值,h[] 区间最大值
int lowbit(int x){return x&(-x);
}
void add (int x, int val) {while (x <= n) {h[x] = max (h[x], val); x += x&(-x);}
}
ll query(int pos){ll ret=-INF;for(int i=pos;i;i-=lowbit(i))ret=max(ret,h[i]);return ret;
}
signed main(){ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);int t;cin>>t;while(t--){cin>>n;for(int i=1;i<=n;i++){cin>>a[i];b[i]=b[i-1]+a[i];sum[i]=sum[i-1]+a[i];c[i]=c[i-1]+a[i];}int m=n;sort(b+1,b+1+n);m=unique(b+1,b+1+m)-b-1;//去重操作,返回不同元素的个数for(int i=1;i<=n;++i){sum[i]=lower_bound(b+1,b+1+m,sum[i])-b;h[i]=-INF;dp[i]=0;}h[0]=-INF;dp[0]=0;for(int i=1;i<=n;i++){if(a[i]<0) dp[i]=dp[i-1]-1;else if(a[i]==0) dp[i]=dp[i-1];else dp[i]=dp[i-1]+1;dp[i]=max(dp[i],query(sum[i]-1)+i);// 			cout<<dp[i]<<" "<<query(sum[i]-1)<<endl;if(c[i]>0) dp[i]=i;add(sum[i],dp[i]-i);}cout<<dp[n]<<endl;}
}

  相关解决方案