【HAOI2008】木棍分割
原题:
n<=5e4,m<=min{n-1,1000},li<=1000
最大的最小
那必然是二分答案
二分一个答案,检查的时候可以贪一个,对于某根木棍,如果它可以并入前一段,那么不并入前一段一定不会比并入前一段更优,这个易证
这样就解决了子问题1
求总长度最大的一段最小的方案数时,因为已经知道了最大值最小是多少,所以直接求所有木棍的长度都<=ans1的方案数
虽然题目要求的是最大值恰好为ans1,但是最大值最小只能是ans1,所以不用担心把最大值小于ans1的方案统计进来
然后容易得到一个二维dp
f[i][j]表示直到第i个木棍,已经拼接了j段的方案数,s表示木棍长度的前缀和
那么显然f[i][j]=∑f[k-1][j-1](s[i]-s[k-1]<=ans1)
优化1:
可以发现s[i]-s[k-1]具有单调性,那么k的取值是一个连续的区间,并且可以由二分快速得到
优化2:
既然k的取值是一个连续区间,那么求出f函数在第一维的前缀和g,就可以快速得到合式的值
这样时间已经OK了,但是空间太大
于是我就卡在这一步……
思索片刻决定扫一眼题解,并豁然开朗,并且认为看题解是明智的选择
可以发现,对于所有f[i][j]都是从f[k][j-1]转移而来,即第二维只与上一层有关
而且两个维度相互独立
那么可以调整dp顺序,先枚举第二维,然后对第二维做滚动数组
易错点:
1.f的初始状态为f[0][0]=1,但是注意,g数组初值不能只赋g[0][0]
2.虽然这道题中f和g的转移都可以直接赋值,滚动数组不用清空,但是g的初值需要清空,否则会出现f[0][2]=1,使得答案错误
3.交换了二分顺序之后不要把找k的范围的二分放在里边,否则时间不正确
因为对于所有的i,k的范围都是一样的,与j无关,所以可以预处理
代码:
1 #include<iostream> 2 #include<cstdio> 3 using namespace std; 4 const int mo=10007; 5 int n,m,a[51000]; 6 int s[51000]; 7 int ans1=0,ans2=0; 8 //int f[51000][1100]; 9 //int g[51000][1100]; 10 int f[51000][2]; 11 int g[51000][2]; 12 int t[51000]; 13 bool chck(int x){ 14 int bwl=0,cnt=1; 15 for(int i=1;i<=n;++i){ 16 if(a[i]>x) return false; 17 if(bwl+a[i]>x){ 18 bwl=0; 19 ++cnt; 20 } 21 bwl+=a[i]; 22 } 23 return cnt<=m; 24 } 25 int bsa(){ 26 int l=1,r=s[n],md; 27 while(l+1<r){ 28 md=(l+r)>>1; 29 (chck(md) ? r : l)=md; 30 } 31 return chck(l) ? l : r; 32 } 33 int bsi(int x){ 34 int l=1,r=x,md; 35 while(l+1<r){ 36 md=(l+r)>>1; 37 (s[x]-s[md-1]<=ans1 ? r : l)=md; 38 } 39 return s[x]-s[l-1]<=ans1 ? l : r; 40 } 41 int main(){ 42 cin>>n>>m; 43 ++m; 44 for(int i=1;i<=n;++i) scanf("%d",&a[i]); 45 for(int i=1;i<=n;++i) s[i]=a[i]+s[i-1]; 46 ans1=bsa(); 47 f[0][0]=1; 48 for(int i=0;i<=n;++i) g[i][0]=1; 49 //注意g的初值不能只赋g[0][0] 50 /* 51 for(int i=1;i<=n;++i){ 52 int tmp=bsi(i); 53 for(int j=1;j<=m && j<=i;++j){ 54 if(tmp>1){ 55 f[i][j]=((f[i][j]+g[i-1][j-1]-g[tmp-2][j-1])%mo+mo)%mo; 56 } 57 else{ 58 f[i][j]=((f[i][j]+g[i-1][j-1])%mo+mo)%mo; 59 } 60 g[i][j]=(g[i-1][j]+f[i][j])%mo; 61 } 62 } 63 */ 64 for(int i=1;i<=n;++i) t[i]=bsi(i); 65 for(int j=1;j<=m;++j){ 66 if(j!=1) g[0][j&1]=0; //注意,当j!=0时g[0][j]应当被清空 67 for(int i=1;i<=n;++i){ 68 int tmp=t[i]; 69 if(tmp>1){ 70 f[i][j&1]=(g[i-1][(j&1)^1]-g[tmp-2][(j&1)^1]+mo)%mo; 71 } 72 else{ 73 f[i][j&1]=g[i-1][(j&1)^1]; 74 } 75 g[i][j&1]=(g[i-1][j&1]+f[i][j&1])%mo; 76 } 77 ans2=(ans2+f[n][j&1])%mo; 78 } 79 //for(int i=0;i<=m;++i) ans2=(ans2+f[n][i])%mo; 80 cout<<ans1<<" "<<ans2<<endl; 81 //注意ans2不是g[n][m] 82 return 0; 83 }