[dp+树状数组优化] LightOJ 1145 - Dice (I)

时间:2022-11-18 19:30:33

题意:给n个骰子,每个骰子有k个面,分别标上1~k,要求n个骰子和为s的方法数。其中1 <= n, k <= 1000, 1 <= s <= 15000。

题解:DP。令DP[i][j]表示前i个骰子和为j的方法数。

DP[1][j] = 1 ( 1 <= j <= k )

DP[i][j] = SUM( DP[i-1][j-p] ) ( 1 <= p <= k && j-p >= 1) 或者 DP[i+1][j+p] += DP[i][j] ( 1 <= p <= k && j+p <= s )

很容易看出这是一个个n*k*s的DP,但是注意到前一种转移写法里面有SUM,可以用树状数组维护。

其实这个可以O(1)维护,强行加log之后复杂度n*k*log(s),数据太弱,成功达成Statistics倒数第一。

#include<stdio.h>
#include<string.h>
#include<algorithm>
using std::min;
const int mod = 100000007;
const int MX = 15005;
int tree[2][MX];
void update(int rt, int pos, int v){
    for(; pos < MX; pos += pos&-pos){
        tree[rt][pos] += v;
        if(tree[rt][pos] >= mod) tree[rt][pos] -= mod;
    }
}
int getsum(int rt, int pos){
    int res = 0;
    for(; pos > 0; pos -= pos&-pos){
        res += tree[rt][pos];
        if(res >= mod) res -= mod;
    }
    return res;
}
int main(){
    int T, ca = 1;
    scanf("%d", &T);
    while(T--){
        int n, k, s;
        scanf("%d%d%d", &n, &k, &s);
        memset(tree, 0, sizeof(tree));
        for(int i = 1; i <= k; ++i) update(1, i, 1);
        int fg = 0;
        for(int i = 2; i <= n; ++i, fg ^= 1){
            int up = min(s, i*k);
            memset(tree[fg], 0, sizeof(tree[fg]));
            for(int j = i; j <= up; ++j){
                int tmp = getsum(fg^1, j-1) - getsum(fg^1, j-k-1);
                if(tmp >= mod) tmp -= mod;
                else if (tmp < 0) tmp += mod;
                update(fg, j, tmp);
            }
        }
        printf("Case %d: %d\n", ca++, (getsum(fg^1, s) - getsum(fg^1, s-1)+mod)%mod);
    }
}