HDU-4532 湫秋系列故事——安排座位 组合数学DP

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/qq_35950004/article/details/83447105

必须写,太经典了。
题意:有来自n个专业的学生,每个专业分别有ai个同学,现在要将这些学生排成一行,使得相邻的两个学生来自不同的专业,问有多少种不同的安排方案。
思路就是隔板法。
设dp[i][j] 为前i个专业,有j对相邻的同系同学的方案数。
目标状态就是dp[n][0]
下面这个转移妙(厚)不(颜)可(无)言(耻)。
通过状态,我们可以发现应该一个系一个系的转移。dp[i-1] -> dp[i]
然后把一个系(i)的人分为k块,C(k-1 , cnt[i]) * k!
然后这k块中前h块隔开了原来相邻的同系同学。C(j,h) *C(sum +1 - j , k)
然后就可以转移了??
这里用Noi.ac 的 #97 Sequence的代码

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define maxn 31
#define mod 1234567891
#define LL long long
using namespace std;

int a[maxn],cnt[1005];
LL dp[2][maxn],C[maxn][maxn],fac[maxn];

int main()
{
	int n,P;
	scanf("%d%d",&n,&P);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]=(a[i]%P+P)%P,cnt[a[i]]++;
	C[0][0]=1;
	fac[0]=1;for(int i=1;i<maxn;i++) fac[i] = fac[i-1] * i % mod;
	for(int i=1;i<maxn;C[i][0]=1,i++)
		for(int j=1;j<=i;j++)
			C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
	int now=1,pre=0,sum=0;
	LL Fac=1;
	dp[pre][0] = 1;
	for(int i=0;i<P;i++)
		if(cnt[i])
		{
			memset(dp[now],0,sizeof dp[now]);
			for(int j=0;j<=sum;j++)
				if(dp[pre][j])
					for(int k=1;k<=cnt[i];k++)
						for(int h=0;h<=j && h<=k;h++)
						{
							dp[now][j-h+cnt[i]-k] = (dp[now][j-h+cnt[i]-k] + 
							dp[pre][j] * C[j][h] % mod * C[sum+1-j][k-h] % mod * C[cnt[i]-1][k-1]) % mod; 
						}
			swap(now,pre);
			sum += cnt[i];
			Fac = (Fac * fac[cnt[i]]) % mod;
		}
		
	printf("%lld\n",(dp[pre][0] * Fac)%mod);
}

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/83447105