题型:数论
题意:
有N堆石子,每堆颜色相同,任意两堆颜色不同。
问所有的石子可以排成多少种不同的序列。
分析:
采用插空法的思想:
设dp(i,j)表示用前i堆石头排成了长度为 j 的序列。
对于dp(i,j)这个状态,由两个状态推出:
(1)不放第 i 堆的石头,直接由前 i-1 堆石头构成长度为 j 的序列,即dp(i-1,j);
(2)在第 i 堆石头中取出x个放进序列中构成长度为 j 的序列,即dp(i-1,j-x)*C(j,x)。C(j,x)就是从j个位置中选出x个位置的组合数。
所以状态转移方程:
dp(i,j) = dp(i-1,j) + ∑(dp(i-1,j-x)*C(j,x))
代码:
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#define LL long long
#define mt(a,b) memset(a,b,sizeof(a))
using namespace std;
const LL mod = 1e9+7;
const int MAXN = 12345;
int n;
LL dp[123][MAXN];
LL c[MAXN][123];
int a[123];
void cal() {
int x = 10000;
int y = 100;
mt(c,0);
int i,j;
for(i=0; i<=y; i++)c[0][i]=c[1][i]=1;
for(i=0; i<=y; i++)c[i][i]=1;
for(i=0; i<=x; i++)c[i][0]=1;
for(i=1; i<=x; i++)
for(j=1; j<=y; j++)
if(i!=j)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
void DP(){
mt(dp,0);
for(int i=1;i<=n;i++){
dp[i][0] = 1;
}
for(int i=1;i<=a[1];i++){
dp[1][i] = 1;
}
int sum = a[1];
for(int i=2;i<=n;i++){
sum += a[i];
for(int j=1;j<=sum;j++){
int minn = min(j,a[i]);
dp[i][j] = dp[i-1][j];
for(int k=1;k<=minn;k++){
dp[i][j] += dp[i-1][j-k]*c[j][k];
dp[i][j] %= mod;
}
}
}
}
int main(){
int _ = 0;
cal();
while(~scanf("%d",&n)){
int sum = 0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum += a[i];
}
DP();
LL ans = 0;
for(int i=1;i<=sum;i++){
ans += dp[n][i];
ans %= mod;
}
printf("Case %d: %lld\n",++_,ans);
}
return 0;
}