BZOJ 1044 木棍分割(二分答案 + DP优化)
题目链接 木棍分割
1044: [HAOI2008]木棍分割
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 3830 Solved: 1453
[Submit][Status][Discuss]
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)
Source
第一个问题显然用二分解决,答案为len
第二个问题考虑DP,f[i][j]表示前i根木棒切j刀之后长度最大的一段不超过len的方案数。
则状态转移方程为 f[i][j] = sum(f[k][j - 1])
其中k必须满足 s[i] - s[k] <= len
这样做的话空间复杂度和时间复杂度都超过了限制,所以我们需要优化
空间复杂度方面,因为f[i][j]是从f[i][j - 1]推导而来,所以f的第二维可以用滚动数组来代替。
时间复杂度方面,我们发现大量的时间浪费在判断s[i] - s[k] <= len上,
因为随着j的递增每次符合s[i] - s[k] <= len的k的最大值是不下降的,
那么维护一个类似two-pointer的东西就可以了。
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) typedef long long LL; const int N = 50010; const int mod = 10007; int n, m; int a[N], s[N], f[N][2]; int l, r, ans; int len, cnt; int k, now; inline bool check(int x){ int now = a[1], ret = 0; rep(i, 2, n){ if (now + a[i] <= x){ now += a[i]; } else{ now = a[i]; ++ret; } } return ret <= m; } int main(){ scanf("%d%d", &n, &m); rep(i, 1, n){ scanf("%d", a + i); l = max(l, a[i]); r += a[i]; s[i] = s[i - 1] + a[i]; } l = 0, r = 1e8; while (l + 1 < r){ int mid = (l + r) >> 1; if (check(mid)) r = mid; else l = mid + 1; } if (check(l)) len = l; else len = r; rep(i, 0, m){ cnt = 0; k = 1; rep(j, 1, n){ if (i == 0){ if (s[j] <= len) f[j][now] = 1; else f[j][now] = 0; } else{ while (k < j && s[j] - s[k] > len){ cnt -= f[k][now ^ 1]; cnt = (cnt + mod) % mod; ++k; } f[j][now] = cnt; } cnt = (cnt + f[j][now ^ 1]) % mod; } (ans += f[n][now]) %= mod; now ^= 1; } printf("%d %d\n", len, ans); return 0; }