CF1582 E. Pchelyonok and Segments
把数组翻转一下
问题变成了每段数字个数增多,总和减少
这样的好处是可以直接求段数的最大值
dp[i][j]表示前i个数可以组合出前j段,且第j段的和最大时的最后一个位置
因为每一段的和逐渐减小,所以前面的和越大越好
j最大是根号级别,所以复杂度是n*sqrt(n)的
转移首先令f[i][j]=f[i-1][j],因为前i-1个数可以的前i个数也可以
然后考虑是否要用i更新f[i][j]
i可以更新f[i][j]有2个条件:
1、以i为结尾的长为j的一段能够接到前i-j个数凑成的j-1个段的后面,即前者的和 小于 后者的第j-1段的和的最大值
2、以i为结尾的长为j的一段的和 大于 原先的以f[i][j]结尾的长为j的一段的和
前缀和可以判断
#include<bits/stdc++.h> using namespace std; #define N 100003 int a[N]; int f[N][447]; long long sum[N]; int main() { int T,n; int m,l,mid,r; scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&a[i]); reverse(a+1,a+n+1); for(int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i]; l=1; r=n; while(l<=r) { mid=l+r>>1; if(1ll*mid*(mid+1)/2<=n) { m=mid; l=mid+1; } else r=mid-1; } for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) f[i][j]=0; for(int i=1;i<=n;++i) { f[i][1]=f[i-1][1]; if(a[i]>a[f[i][1]]) f[i][1]=i; } for(int i=1;i<=n;++i) for(int j=2;j<=m && i-j>=0 && f[i-j][j-1];++j) { f[i][j]=f[i-1][j]; if(sum[i]-sum[i-j]<sum[f[i-j][j-1]]-sum[f[i-j][j-1]-j+1]) { if(f[i][j]) { if(sum[i]-sum[i-j]>sum[f[i][j]]-sum[f[i][j]-j]) f[i][j]=i; } else f[i][j]=i; } } for(int i=m;i;--i) if(f[n][i]) { printf("%d\n",i); break; } } }