1. 程式人生 > >【BZOJ4665】小w的喜糖 容斥+組合數

【BZOJ4665】小w的喜糖 容斥+組合數

原來 ans lin log style pre 交換 amp rip

【BZOJ4665】小w的喜糖

Description

廢話不多說,反正小w要發喜糖啦!! 小w一共買了n塊喜糖,發給了n個人,每個喜糖有一個種類。這時,小w突發奇想,如果這n個人相互交換手中的糖,那會有多少種方案使得每個人手中的糖的種類都與原來不同。 兩個方案不同當且僅當,存在一個人,他手中的糖的種類在兩個方案中不一樣。

Input

第一行,一個整數n 接下來n行,每行一個整數,第i個整數Ai表示開始時第i個人手中的糖的種類 對於所有數據,1≤Ai≤k,k<=N,N<=2000

Output

一行,一個整數Ans,表示方案數模1000000009

Sample Input

6
1
1
2
2
3
3

Sample Output

10

題解:顯然我們應該將每種糖果放在一起處理,用v[i]表示有多少人有第i種糖果。然後考慮容斥,用f[i][j]表示前i種糖果,至多j個人的糖果與原來不同的方案數,然後很容易DP求出f數組。

$f[i][j]=\sum\limits_{k=0}^{v[i]}f[i-1][j-k]*C_{v[i]}^{k}*(v[i])*(v[i]-1)*...*(v[i]-k+1)$

發現我們在DP過程中並沒有考慮我們選出來那j個人的順序,所以最後f[i][j]乘上j!即可。最後因為每個糖果是相同的,所以方案數要除以v[i]!。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const ll P=1000000009;
int n,m;
ll ans;
int col[2010],s[2010],v[2010];
ll c[2010][2010],f[2010][2010],jc[2010],ine[2010],jcc[2010];
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)	f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+gc-‘0‘,gc=getchar();
	return ret*f;
}
int main()
{
	n=rd();
	int i,j,k;
	for(i=0;i<=n;i++)
	{
		c[i][0]=1;
		for(j=1;j<=i;j++)	c[i][j]=(c[i-1][j-1]+c[i-1][j])%P;
	}
	jc[0]=ine[0]=jcc[0]=jc[1]=ine[1]=jcc[1]=1;
	for(i=2;i<=n;i++)
	{
		jc[i]=jc[i-1]*i%P,ine[i]=P-(P/i)*ine[P%i]%P,jcc[i]=jcc[i-1]*ine[i]%P;
	}
	for(i=1;i<=n;i++)	col[i]=rd();
	sort(col+1,col+n+1);
	for(i=1;i<=n;i++)
	{
		if(col[i]>col[i-1])	m++;
		v[m]++;
	}
	for(i=1;i<=m;i++)	s[i]=s[i-1]+v[i];
	f[0][0]=1;
	for(i=1;i<=m;i++)	for(j=0;j<=s[i-1];j++)	for(k=0;k<=v[i];k++)
		f[i][j+k]=(f[i][j+k]+f[i-1][j]*c[v[i]][k]%P*jc[v[i]]%P*jcc[v[i]-k]%P)%P;
	for(i=0;i<=n;i++)
	{
		ans=(ans+((i&1)?-1:1)*f[m][i]*jc[n-i]+P)%P;
	}
	for(i=1;i<=m;i++)	ans=ans*jcc[v[i]]%P;
	printf("%lld",ans);
	return 0;
}

【BZOJ4665】小w的喜糖 容斥+組合數