【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 }
View Code
原文地址:https://www.cnblogs.com/cdcq/p/12661220.html