【题解】
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)的递推
【吐槽】
我要吐槽,因为我被这题坑了一个晚上
看网上的博客都写了二分答案的解法,可偏偏我就用了斜率优化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
【代码】
#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;
}
2019.7.4
准备计算机系夏令营,复习DP时又看见这道题。再次拿出来做,再次坑一晚上,终于A掉了。
附上代码,第一问二分答案,第二问状压DP。
#include<cstdio>
#define maxN 50005
#define MOD 10007
using namespace std;
int a[maxN],S[maxN],g[maxN][2],Sg[maxN];
int n,m;
int Min(int a,int b)
{
if(a<b) return a;
return b;
}
int Max(int a,int b)
{
if(a>b) return a;
return b;
}
int judge(int x)
{
int i,sum=0,cnt=0;
for(i=1;i<=n;i++)
{
if(a[i]>x) return 0;
if(sum+a[i]<=x) sum+=a[i];
else
{
cnt++;
sum=a[i];
}
}
if(cnt>m) return 0;
return 1;
}
int main()
{
int i,j,k,maxa=0,left,right,mid,ans1,ans2=0;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(maxa<a[i]) maxa=a[i];
S[i]=a[i]+S[i-1];
}
left=maxa;
right=S[n];
while(left<right)
{
mid=(left+right)/2;
if(judge(mid)>0) right=mid;
else left=mid+1;
}
ans1=left;
for(i=1;i<=n;i++)
{
if(S[i]<=ans1) g[i][0]=1;
else g[i][0]=0;
}
ans2=g[n][0];
for(j=1;j<=m;j++)
{
Sg[j-1]=0;
Sg[j]=g[j][(j-1)&1];
k=j-1;
for(i=j+1;i<=n;i++)
{
while(S[i]-S[k+1]>ans1) k++;
g[i][j&1]=(Sg[i-1]-Sg[k]+MOD)%MOD;
Sg[i]=(Sg[i-1]+g[i][(j-1)&1]+MOD)%MOD;
}
ans2=(ans2+g[n][j&1]+MOD)%MOD;
}
printf("%d %d",ans1,ans2);
return 0;
}