[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;
}
posted @ 2020-08-11 23:00  Achtoria  阅读(96)  评论(0编辑  收藏  举报