木棍分割[HAOI2008]
题目描述
有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连
接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长
度最大的一段长度最小. 并将结果mod 10007。。。
输入
输入文件第一行有2个数n,m.接下来n行每行一个正整数Li,表示第i根木棍的长度.n<=50000,0<=m<=min(n-1,10
00),1<=Li<=1000.
输出
输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.
样例输入
3 2
1
1
10
样例输出
10 2
提示
两种砍的方法: (1)(1)(10)和(1 1)(10)
题解
人生第一道二分答案成就感满满!二分答案,是指在最小值和最大值之间二分枚举答案,逐渐把答案缩小到一个值得出最优解。二分答案的题目设问常常是最大值最小、最小值最大,解的可能性应该是单调的(即某值不可行则答案只能比它大或只能比它小),需要能简单地验证答案正确与否,要注意的就是防止死循环(不过这道题好像没有这个问题)。对于木棍分割,如果这个值可行,最优解可能比它更小,以它为上界看一看有没有更小的解;如果这个值不可行,只能舍弃它和比它更小的解,以它+1为下界搜索答案。 int check(long long x) { temp=he=0; for(int i=1;i<=n;i++) { if(he+li[i]<=x) he+=li[i]; else { he=li[i]; temp++; if(temp>m) return 0; } } if(temp>m) return 0; return 1; } le=yd,jg=zd;(最小解是单段木块长度的最大值,最大解是所有木块长度之和) while(le<jg) { mid=(le+jg)>>1; if(check(mid)) jg=mid; else le=mid+1; } dp部分优化空间复杂度用滚动数组,优化时间复杂度用类似单调队列的思路,和Watching firework is fun大同小异,最后结果为f[i][n]之和。f[i][j]表示j段木块切i次的方案,f[i][j]为所有满足sum[i]-sum[k]<=jg的f[k][j-1]之和(1<=k<i)。f[i]只与f[i-1]有关,第一维只有0和1;sum是单调的,只要维护一个f[k][j-1]之和每次从后部加上新值,从头部减去不满足条件的k对应的值即可。
UPD:关于这个二分卡死的问题后来慢慢弄清了是怎么回事。并不是+1或不+1就容易被卡死,而是你要保证每次l和r总有一个是变化的。比如说mid=l+r>>1,那mid可能和l相等,你就不能if(……)l=mid,如果必须这样就得mid=(l+r+1)>>1。OI中的很多东西开始不明白,用了很长时间忽然就搞懂了,只有看清了内部的原理才是真正的通透啊。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int sj=50010; int li[sj],mod=10007,yd; int la,no,head,n,m,mid,temp,tail; long long s[sj],f[2][sj],zd,jg,le,he,fas; int dbj(int x,int y) { return x>y?x:y; } int xbj(int x,int y) { return x<y?x:y; } void init() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&li[i]); s[i]=s[i-1]+li[i]; zd+=li[i]; yd=dbj(li[i],yd); } } void dp() { for(int i=1;i<=n;i++) { if(s[i]<=jg) f[0][i]=1; if(s[i]>jg) break; } fas=f[0][n]; for(int j=1;j<=m;j++) { head=0; tail=0; he=0; la=(j+1)%2; no=j%2; for(int i=1;i<=n;i++) { if(i<j) continue; for(int k=tail+1;k<i;k++) he+=f[la][k]; tail=i-1; while(s[i]-s[head]>jg) { he-=f[la][head]; head++; } f[no][i]=he; f[no][i]%=mod; } fas+=f[no][n]; fas%=mod; } } int check(long long x) { temp=he=0; for(int i=1;i<=n;i++) { if(he+li[i]<=x) he+=li[i]; else { he=li[i]; temp++; if(temp>m) return 0; } } if(temp>m) return 0; return 1; } int main() { init(); le=yd,jg=zd; while(le<jg) { mid=(le+jg)>>1; if(check(mid)) jg=mid; else le=mid+1; } printf("%lld\n",jg); dp(); printf("%lld",fas); //while(1); return 0; }
南风知我意,吹梦到西洲。