●BZOJ 4665 小w的喜糖

题链:

http://www.lydsy.com/JudgeOnline/problem.php?id=4665

题解:

容斥,dp
令 v[i] 表示原来拥有i类糖果的人数
(一个套路,首先把每个糖果看成互不相同的,(最后再来除以 v[i]!,把同种糖果看成相同的) )
定义 dp[i][j]表示前i类糖果,有j个人的糖果和原来的一样,其他 N-j 个人暂时不拿糖果的方案数。
这个的转移如下:
对于已经确定的 i,j,枚举一个 k表示原来拥有的是第i类糖果的k个人任然拿到的是第i类糖果。
${dp[i][j]=dp[i-1][j-k]}\times{{C}_{v[i]}^{k}}\times{v[i]}\times{(v[i]-1)}\times{…}\times{(v[i]-k+1)}$
式子后面乘的东西意思是那k个人在第i类糖果中的选择方法数(每个糖果看成不一样的了)
然后就再把每一个 dp[N][i] * (N-i)! 表示剩下的人就随便选择糖果。
此时 dp[N][i]的含义即为:重新分配糖果后,至少有 i个人拿到了原来的那类糖果的方法数。
然后就是考虑容斥,答案 ANS=dp[N][0]-dp[N][1]+dp[N][2]-dp[N][3]......
最后还要把同类糖果看成相同的东西,即 ANS/=v[i]。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 2500
#define _ %mod
#define filein(x) freopen(#x".in","r",stdin)
#define fileout(x) freopen(#x".out","w",stdout)
using namespace std;
const int mod=1000000009;
int v[MAXN],dp[MAXN][MAXN],C[MAXN][MAXN],fac[MAXN],inv[MAXN];
int N,ANS;
int pow(int a,int b){
	int now=1;
	while(b){
		if(b&1) now=(1ll*now*a)_;
		a=(1ll*a*a)_; b>>=1;
	}
	return now;
}
int main()
{
	scanf("%d",&N); 
	fac[0]=inv[0]=1;
	for(int i=1;i<=N;i++) fac[i]=(1ll*fac[i-1]*i)_;
	inv[N]=pow(fac[N],mod-2);
	for(int i=N-1;i>=1;i--) inv[i]=(1ll*inv[i+1]*(i+1))_;
	for(int i=0;i<=N;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(1ll*C[i-1][j-1]+C[i-1][j])_;
	}
	for(int i=1,x;i<=N;i++) scanf("%d",&x),v[x]++;
	dp[0][0]=1;
	for(int i=1;i<=N;i++)
		for(int j=0;j<=N;j++)
			for(int k=0;k<=v[i];k++){
				if(k>j) break;
				dp[i][j]=(1ll*dp[i][j]+1ll*dp[i-1][j-k]*C[v[i]][k]_*fac[v[i]]_*inv[v[i]-k])_;
			}
	for(int i=0;i<=N;i++){
		dp[N][i]=1ll*dp[N][i]*fac[N-i]_;
		if(i&1) dp[N][i]=(1ll*dp[N][i]*(-1)+mod)_;
		ANS=(1ll*ANS+dp[N][i]+mod)_;
	}
	for(int i=1;i<=N;i++) ANS=1ll*ANS*inv[v[i]]_;
	printf("%d",ANS);
	return 0;
}

posted @ 2017-12-13 21:49  *ZJ  阅读(217)  评论(0编辑  收藏  举报