題目連結: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[][];
const int mod = ;
int main(){
int t, n, k, s, Case=;
scanf("%d", &t);
while(t--){
scanf("%d%d%d", &n, &k, &s);
for(int i=; i<=k; ++i) dp[][i] = ;
for(int i=k+; i<=s; ++i) dp[][i] = ;
for(int i=; i<=n; ++i){
for(int j=; j<=s; ++j){
if(j-k->=)
dp[i&][j] = ((dp[i&][j-] + dp[(i-)&][j-])%mod + mod - dp[(i-)&][j-k-]) % mod;
else
dp[i&][j] = ((dp[i&][j-] + dp[(i-)&][j-])%mod + mod - dp[(i-)&][]) % mod;
}
}
printf("Case %d: %lld\n", ++Case, dp[n&][s]);
}
return ;
}