【动态规划】bzoj1044: [HAOI2008]木棍分割
需要滚动优化或者short int卡空间
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
3 2
1
1
10
1
1
10
Sample Output
10 2
HINT
两种砍的方法: (1)(1)(10)和(1 1)(10)
题目分析
昨天做到这道题的稍微强化版:允许空集存在。我那题想法是,强制没有空集,最后再用组合数统计答案。状态$f[i][j]$表示前$i$个数分为$j$个非空集合的方案数,$t[i]$表示最小能够与$i$合并的位置。于是$f[i][j]=\sum_{x=t[i]}^{i}{f[i][j-1]}$。这里有一个$\sum_{i}{f[i][j]}$的形式,自然用前缀和优化。
组合数溢出调了好久……
1 #include<bits/stdc++.h> 2 const int maxn = 1003; 3 const int MO = 998244353; 4 5 int n,m,mxBound,mnBound,ans; 6 int a[maxn],s[maxn],t[maxn],g[maxn][maxn]; 7 int f[maxn][maxn]; 8 int C[maxn][maxn]; 9 10 int read() 11 { 12 char ch = getchar(); 13 int num = 0; 14 bool fl = 0; 15 for (; !isdigit(ch); ch = getchar()) 16 if (ch=='-') fl = 1; 17 for (; isdigit(ch); ch = getchar()) 18 num = (num<<1)+(num<<3)+ch-48; 19 if (fl) num = -num; 20 return num; 21 } 22 bool check(int x) 23 { 24 int cnt = 1, sum = 0; 25 for (int i=1; i<=n; i++) 26 if (sum+a[i] > x) sum = a[i], cnt++; 27 else sum += a[i]; 28 return cnt <= m; 29 } 30 int qmi(int a, int b) 31 { 32 int ret = 1; 33 while (b) 34 { 35 if (b&1) ret = 1ll*ret*a%MO; 36 a = 1ll*a*a%MO, b >>= 1; 37 } 38 return ret; 39 } 40 int main() 41 { 42 // freopen("ex_t2.in","r",stdin); 43 n = read(), m = read(), C[0][0] = 1; 44 for (int i=1; i<=m; i++) 45 { 46 C[i][0] = 1; 47 for (int j=1; j<=i; j++) 48 C[i][j] = (C[i-1][j]+C[i-1][j-1])%MO; 49 } 50 for (int i=1; i<=n; i++) 51 a[i] = read(), mxBound += a[i], mnBound = mnBound < a[i]?a[i]:mnBound, s[i] = s[i-1]+a[i]; 52 int l = mnBound, r = mxBound, head = 1, tot = 0; 53 for (int mid=(l+r)>>1; l<=r; mid=(l+r)>>1) 54 if (check(mid)) ans = mid, r = mid-1; 55 else l = mid+1; 56 printf("%d\n",ans); 57 for (int i=1; i<=n; i++) 58 { 59 tot += a[i]; 60 while (tot > ans) tot -= a[head++]; 61 t[i] = head; 62 } 63 g[0][0] = f[0][0] = 1, ans = 0; 64 for (int i=1; i<=n; i++) 65 { 66 g[i][0] = 1; 67 for (int j=1; j<=m; j++) 68 { 69 int delta = g[i-1][j-1]; 70 if (t[i]>1) delta -= g[t[i]-2][j-1]; 71 (f[i][j] += 1ll*delta+MO) %= MO; 72 (g[i][j] = 1ll*g[i-1][j]+1ll*f[i][j]) %= MO; 73 } 74 } 75 for (int i=1; i<=m; i++) 76 ans = (ans+1ll*C[m][i]*f[n][i])%MO; 77 printf("%d\n",ans); 78 return 0; 79 }
但是强化版空间512M,这题却只有162M。
首当其冲想到滚动数组优化,不过由于在这题里滚动数组需要把$j$放在枚举的外层,因此在一些奇怪的原因影响下效率会变得非常低。
注意到本题的模数非常小(不知道是不是出题人的善意),于是可以把$f[i][j]$开成short int省去一半空间。
再注意到其实同时开$f[i][j]$与$g[i][j]$是没有必要的,我们完全可以把$f[i][j]$表示成$g[i][j]$的前缀和形式,从而又省去一半空间。
1 #include<bits/stdc++.h> 2 const int maxn = 50003; 3 const int maxm = 1003; 4 const int MO = 10007; 5 6 int n,m,mxBound,mnBound,ans; 7 int a[maxn],s[maxn],t[maxn]; 8 short int g[maxn][maxm]; 9 10 int read() 11 { 12 char ch = getchar(); 13 int num = 0; 14 bool fl = 0; 15 for (; !isdigit(ch); ch = getchar()) 16 if (ch=='-') fl = 1; 17 for (; isdigit(ch); ch = getchar()) 18 num = (num<<1)+(num<<3)+ch-48; 19 if (fl) num = -num; 20 return num; 21 } 22 bool check(int x) 23 { 24 int cnt = 1, sum = 0; 25 for (int i=1; i<=n; i++) 26 if (sum+a[i] > x) sum = a[i], cnt++; 27 else sum += a[i]; 28 return cnt <= m; 29 } 30 int main() 31 { 32 // freopen("1044.in","r",stdin); 33 // freopen("1044.out","w",stdout); 34 n = read(), m = read()+1; 35 for (int i=1; i<=n; i++) 36 a[i] = read(), mxBound += a[i], mnBound = mnBound < a[i]?a[i]:mnBound, s[i] = s[i-1]+a[i]; 37 int l = mnBound, r = mxBound, head = 1, tot = 0; 38 for (int mid=(l+r)>>1; l<=r; mid=(l+r)>>1) 39 if (check(mid)) ans = mid, r = mid-1; 40 else l = mid+1; 41 printf("%d ",ans); 42 for (int i=1; i<=n; i++) 43 { 44 tot += a[i]; 45 while (tot > ans) tot -= a[head++]; 46 t[i] = head; 47 } 48 register int i,j; 49 ans = 0; 50 for (i=0; i<=n; i++) g[i][0] = 1; 51 for (int i=1; i<=n; i++) 52 { 53 for (int j=1; j<=m; j++) 54 { 55 int delta = g[i-1][j-1]; 56 if (t[i]>1) delta -= g[t[i]-2][j-1]; 57 (g[i][j] = g[i-1][j]+delta) %= MO; 58 } 59 } 60 for (i=1; i<=m; i++) 61 ans = (ans+g[n][i]-g[n-1][i]+MO)%MO; 62 printf("%d\n",ans); 63 return 0; 64 }
END