题目链接:LightOJ 1145
题意:
有n个骰子,每个骰子有k个面,每个面有一个权值,为该面的下标,现在用用n个骰子凑出面值s,问有多少种方法。
思路:
设dp[i][j]表示第i个骰子凑出了j有dp[i][j]种方法。
那么dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] + … + dp[i-1][j-k]
那么状态有n*s个,转移花费k,显然会超时。
观察发现:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] + … + dp[i-1][j-k]
dp[i][j-1] = dp[i-1][j-2] + dp[i-1][j-3] + … + dp[i-1][j-k-1]
联立一下得到:
dp[i][j] = dp[i][j-1] - dp[i-1][j-k-1] + dp[i-1][j-1]
这样转移就是O(1)了。
然后MLE了。。。
n*s = 1500W * 4byte = 60 000 000 byte = 60MB > 32MB
再次观察发现每次只用到dp[i]和dp[i-1],这样可以用dp[2][s]大的数组滚动处理就可以了。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
long long dp[2][15005];
const int mod = 100000007;
int main(){
int t, n, k, s, Case=0;
scanf("%d", &t);
while(t--){
scanf("%d%d%d", &n, &k, &s);
for(int i=1; i<=k; ++i) dp[1][i] = 1;
for(int i=k+1; i<=s; ++i) dp[1][i] = 0;
for(int i=2; i<=n; ++i){
for(int j=1; j<=s; ++j){
if(j-k-1>=0)
dp[i&1][j] = ((dp[i&1][j-1] + dp[(i-1)&1][j-1])%mod + mod - dp[(i-1)&1][j-k-1]) % mod;
else
dp[i&1][j] = ((dp[i&1][j-1] + dp[(i-1)&1][j-1])%mod + mod - dp[(i-1)&1][0]) % mod;
}
}
printf("Case %d: %lld\n", ++Case, dp[n&1][s]);
}
return 0;
}