BZOJ 1044 木棍分割(二分答案 + DP优化)

时间:2021-10-30 00:36:28

题目链接  木棍分割

1044: [HAOI2008]木棍分割

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 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

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;
}