HDU_4248_A Famous Stone Collector(组合数学+DP)

题型:数论


题意:

有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
#include
#include
#include
#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;
}


你可能感兴趣的:(ACM)