【BZOJ1044】[HAOI2008]木棍分割
Description
有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。。。
Input
输入文件第一行有2个数n,m.接下来n行每行一个正整数Li,表示第i根木棍的长度.n<=50000,0<=m<=min(n-1,1000),1<=Li<=1000.
Output
输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.
Sample Input
3 2
1
1
10
1
1
10
Sample Output
10 2
HINT
两种砍的方法: (1)(1)(10)和(1 1)(10)
题解:第一问直接二分答案即可,第二问显然是DP。
为了方便转移,我们需要对于每个位置i,求出pre[i]表示i的上一个砍断位置最远是多少,这个用双指针法和二分都可以搞。然后设f[i][j]表示在前i个连接处砍了j刀的方案数,那么用前缀和优化转移即可。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int P=10007; int n,m,ans,len; int v[50010],s[50010],f[2][50010],sf[2][50010],pre[50010]; inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-')f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar(); return ret*f; } bool solve(int x) { int i,sum=0,last=0; for(i=1;i<=n&&sum<=m;i++) if(s[i]-s[last]>x) { if(last==i-1) return 0; last=i-1,sum++; } sum++; if(sum>m) return 0; return 1; } int main() { n=rd(),m=rd()+1; int i,j,k,l=0,r=0,mid; for(i=1;i<=n;i++) v[i]=rd(),s[i]=s[i-1]+v[i],l=max(l,v[i]),r+=v[i]; while(l<r) { mid=(l+r)>>1; if(solve(mid)) r=mid; else l=mid+1; } printf("%d ",len=r); for(i=1;i<=n;i++) { l=0,r=i; while(l<r) { mid=(l+r)>>1; if(s[i]-s[mid]<=len) r=mid; else l=mid+1; } pre[i]=r; } f[0][0]=1; for(i=0;i<=n;i++) sf[0][i]=1; for(i=1;i<=m;i++) { k=(i&1); sf[k][0]=0; for(j=1;j<=n;j++) { f[k][j]=(sf[k^1][j-1]-((!pre[j])?0:sf[k^1][pre[j]-1])+P)%P; sf[k][j]=(sf[k][j-1]+f[k][j])%P; } ans=(ans+f[k][n])%P; } printf("%d",ans); return 0; }