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,10
00),1<=Li<=1000.
Output
输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.
Sample Input
1
1
10
Sample Output
HINT
两种砍的方法: (1)(1)(10)和(1 1)(10)
正解:二分答案+DP
解题报告:
第一问二分答案随便艹,关键是第二问。
我们第一问已经求出了,最优长度,那么根据这个长度我们需要统计方案数。显然我们可以直接DP,转移很好转移直接f[i]=∑f[j](pre[i]<=j<i),pre[i]表示i能往前拓展的最大位置,f[i]表示i作为一个右端点的方案数。那么显然pre[i]+1到j之间可以构成一个新的段,不会超过长度限制,所以pre[i]到i-1之间的任何一个结点作为上一个区间的右端点的方案都可以转移到当前的位置上去。
但是空间和时间都不允许。考虑我们每次是要求已经划分出p段的方案,显然只能由划分出p-1段的方案转移过来,那么可以滚动数组,只保留上次的方案。时间复杂度仍然很高,怎么办呢?考虑我们每次都是求得连续一段的和,肯定可以用前缀和优化一下。所以每次做完之后,先构一遍上次的方案前缀和,就可以做到O(1)转移了。
有很多细节问题,开始没策清楚,调了好久,还召唤了遥遥才调出来的,二分答案还打错了一个地方,没戏了没戏了。
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 50011; 21 const int MOD = 10007; 22 int n,m,a[MAXN],l,r,mid,tot,ans,ans1; 23 int f[2][MAXN],pre[MAXN],sum[MAXN]; 24 25 inline int getint() 26 { 27 int w=0,q=0; 28 char c=getchar(); 29 while((c<'0' || c>'9') && c!='-') c=getchar(); 30 if (c=='-') q=1, c=getchar(); 31 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 32 return q ? -w : w; 33 } 34 35 inline bool check(int x){ 36 tot=0; int kuai=1; 37 for(int i=1;i<=n;i++) { 38 if(tot+a[i]>x) { 39 kuai++; if(kuai>m+1) return false; 40 tot=a[i]; continue; 41 } 42 tot+=a[i]; 43 } 44 return true; 45 } 46 47 inline void DP(){ 48 int now=1,tag=0; 49 for(int i=1;i<=n;i++) { 50 while(sum[i]-sum[now]>ans) now++; 51 pre[i]=now; //表示上一个段的右端点最左可以为pre[i] 52 } 53 for(int i=1;i<=n;i++) if(sum[i]>ans) break; else f[tag][i]=1; 54 for(int i=1;i<=n;i++) f[tag][i]+=f[tag][i-1]; 55 56 for(int o=2;o<=m+1;o++) { 57 tag^=1; 58 for(int i=1;i<=n;i++) f[tag][i]=f[tag^1][i-1]-f[tag^1][pre[i]-1],f[tag][i]%=MOD; 59 ans1+=f[tag][n]; ans1%=MOD; 60 for(int i=1;i<=n;i++) f[tag][i]+=f[tag][i-1],f[tag][i]%=MOD; 61 } 62 printf("%d",(ans1+MOD)%MOD); 63 } 64 65 inline void work(){ 66 n=getint(); m=getint(); for(int i=1;i<=n;i++) a[i]=getint(),tot+=a[i],l=max(a[i],l),sum[i]=sum[i-1]+a[i]; 67 r=tot; ans=tot; 68 while(l<=r) { 69 mid=(l+r)/2; 70 if(check(mid)) r=mid-1,ans=mid; 71 else l=mid+1; 72 } 73 printf("%d ",ans); 74 DP(); 75 } 76 77 int main() 78 { 79 work(); 80 return 0; 81 }