我要吐槽,因为我被这题坑了一个晚上
看网上的博客都写了二分答案的解法,可偏偏我就用了斜率优化dp的分析思路
“设f[i][j]:前i个数分j段的最小值 ……”
竟然还分析出来了。。。
无奈各种诸如<,<=这样的边界情况巨坑,导致我推了一页纸+调了一晚上
结果第一问的f[n][m&1]求对了,然而第二问的cnt[n][m&1]并不能在f[i][j]转移到f[n][m&1]时累加cnt[i][j]
为什么:3145切两刀,求f[3][2],显然这唯一一刀切在31和4中间最优,这个子问题答案是4
可以通过f[3][2]推得f[4][3]=5,然而cnt[4][3]并不是"+=cnt[3][2]",因为答案是5,前三个数分两段,刀也可以切在3和14中间,这被漏掉了。。。
所以总共写了两个dp,在COGS上过掉后,放到BZOJ,竟然TLE了
这一晚上真是逗QAQ
实在懒得改了,把第一问单调性优化dp的摆上来吧
另:纪念博客100篇 QAQ
【题解】
f[i][j]:前i个数分j段的最小值
设 f[i][x]:前i个数分j段的最小值
f[i][x]=min{ max(f[j][x-1],s[i]-s[j]) }
二分答案即可
然而我的方法类似于斜率优化:
假设j比k优,讨论j,k的大小关系,可得(只写最后结论):
1) j<k(前优) f[j][x-1]<f[k][x-1] 且 s[j]+f[k][x-1]>s[i]
2) k<j(后优) s[k]+f[j][x-1]
可以根据这个,用单调队列维护一个 j<k时s[j]+f[k][x-1]递增的东西,O(m*n)得出f数组
设 cnt[i][x]:第一问答案ans1确定后,前i个数分j段,每段长不超过ans1的方案数
cnt[i][x]=sigma( cnt[j][x-1] ),其中 s[i]-s[j]<=ans1; j<i
注意到对于同一个x,随着i的增大,j的可行区间左端点是不断右移的,而右端点为i-1,因此用k记录左端点,用sum记录区间和,即可做到O(m*n)的递推
【代码】
#include<stdio.h> #include<stdlib.h> #define MOD 10007 int s[50005],f[50005][2],cnt[50005][2],q[50005]; int max(int a,int b) { if(a>b) return a; return b; } int main() { int n,m,i,j,k,head,tail,ans,sum; scanf("%d%d",&n,&m); m++; for(i=1;i<=n;i++) { scanf("%d",&s[i]); s[i]+=s[i-1]; } for(i=1;i<=n;i++) f[i][1]=s[i]; for(j=2;j<=m;j++)//f[i][j]:前i个数分j段的最小值 { head=0; tail=1; q[0]=j-1; for(i=j;i<=n;i++) { while( tail-head>1 && s[q[head]]+f[q[head+1]][j-1&1] < s[i] ) head++; f[i][j&1]=max(f[q[head]][j-1&1],s[i]-s[q[head]]); while( tail-head>1 && f[q[tail-2]][j-1&1]<f[q[tail-1]][j-1&1] && s[q[tail-2]]+f[q[tail-1]][j-1&1] > s[q[tail-1]]+f[i][j-1&1] ) tail--; q[tail++]=i; } } ans=f[n][m&1]; cnt[0][0]=cnt[0][1]=1; for(j=1;j<=m;j++) { k=sum=0; for(i=1;i<=n;i++) { for(;s[i]-s[k]>ans;k++) sum-=cnt[k][j-1&1]; sum+=cnt[i-1][j-1&1]; cnt[i][j&1]=sum%MOD;//别忘取模! } } printf("%d %d",ans,cnt[n][m&1]); return 0; }