1. 程式人生 > >HDU-4532 湫秋系列故事——安排座位 組合數學DP

HDU-4532 湫秋系列故事——安排座位 組合數學DP

必須寫,太經典了。
題意:有來自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);
}