[HAOI2008]木棍分割
第一问直接使用二分 \(+\) 贪心即可。
对于第二问,我们可以考虑一个 \(dp\),令 \(dp_{i, j, k}\) 表示当前选到第 \(i\) 根木棍,已经分成了 \(j\) 段的方案数,当前这一段的长度和为 \(k\) 的方案,那么就有转移就十分显然了。
到这里我们发现这个 \(dp\) 的复杂度只能从状态下手,这个时候我们一般会考虑压掉一维状态并改写状态的意义。可以发现我们令 \(dp_{i, j}\) 表示当前选到第 \(i\) 根木棍,已经分成了 \(j\) 段的方案数,这样是可以转移的:
\[dp_{i, j} = \sum\limits_{k = {li}_i} dp_{k, j - 1}
\]
令 \(mx\) 为第一问的答案,其中 \({li}_i\) 表示 \(i\) 左边第一个 \(\sum\limits_{j = l} ^ i a_j > mx\) 的 \(l\),上面那个转移可以使用前缀和优化,再使用滚动数组优化掉空间即可。
#include<bits/stdc++.h>
using namespace std;
#define N 50000 + 5
#define Mod 10007
#define rep(i, l, r) for(int i = l; i <= r; ++i)
int n, m, l, r, p, ans, a[N], s[N], li[N], dp[2][N], S[2][N];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
bool check(int k){
int cnt = 0, tmp = 0;
rep(i, 1, n){
if(tmp + a[i] > k) ++cnt, tmp = a[i];
else tmp += a[i];
}
return cnt <= m;
}
int Inc(int a, int b){
return (a += b) >= Mod ? a - Mod : a;
}
int Dec(int a, int b){
return (a -= b) < 0 ? a + Mod : a;
}
int main(){
n = read(), m = read();
rep(i, 1, n) a[i] = read(), s[i] = s[i - 1] + a[i], l = max(l, a[i]), r += a[i];
while(l < r){
int Mid = (l + r) / 2;
if(check(Mid)) r = Mid;
else l = Mid + 1;
}
printf("%d ", r);
rep(i, 1, n){
while(p < i && s[i] - s[p] > r) ++p;
li[i] = p;
}
p = 0, ans = (s[n] <= r);
rep(i, 1, n) dp[1][i] = (s[i] <= r), S[1][i] = Inc(S[1][i - 1], dp[1][i]);
rep(i, 2, m + 1){
rep(j, 1, n){
dp[p][j] = S[p ^ 1][j - 1];
if(li[j] > 0) dp[p][j] = Dec(dp[p][j], S[p ^ 1][li[j] - 1]);
}
rep(j, 1, n) S[p][j] = Inc(S[p][j - 1], dp[p][j]);
p ^= 1, ans = Inc(ans, dp[p ^ 1][n]);
rep(j, 1, n) dp[p][j] = 0;
}
printf("%d", ans);
return 0;
}