题意:给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);
}
}