【BZOJ 4665】 4665: 小w的喜糖 (DP+容斥)

时间:2023-03-09 06:12:25
【BZOJ 4665】 4665: 小w的喜糖 (DP+容斥)

4665: 小w的喜糖

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 94  Solved: 53

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

HINT

Source

【分析】

  DP+容斥类的题目都不是很会做啊QWQ。

  人不分顺序,同一种糖果要一起做。

  f[i][j]表示前i种糖果,至少有j个人的是不合法的方案数。

  f[i][j]=f[i-1][j-k]*c[a[i]][k]*(a[i]*(a[i]-1)*(a[i]-2)**[乘k次])

  最后把其他的全排列f[n][i]=f[n][i]*(n-i)!

  然后容斥,if(i&1) ans-=f[n][i] else ans+=f[n][i];

  然后ans/(a[i]!)

  先把所有糖果都看成不一样的,最后除以每种糖果的数量的阶乘,就能保证本质不同了。

  ORZ Claris。。

  学会了不用全部开LL的方法了,在前面加一个1LL* 然后每次乘完就Mod

  中间用到了线性求逆元,复习一下:ny[i]=(Mod-Mod/i)*ny[Mod%i]%Mod

 #include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define Mod 1000000009
#define Maxn 2010
#define LL long long int a[Maxn],c[Maxn][Maxn],pw[Maxn],ny[Maxn];
int f[Maxn][Maxn]; int main()
{
int n;
scanf("%d",&n);
memset(a,,sizeof(a));
for(int i=;i<=n;i++)
{
int x;
scanf("%d",&x);
a[x]++;
}
for(int i=;i<=n;i++) c[i][]=;
for(int i=;i<=n;i++)
for(int j=;j<=i;j++)
{
c[i][j]=(c[i-][j-]+c[i-][j])%Mod;
}
int ans=;
pw[]=;
for(int i=;i<=n;i++) pw[i]=1LL*pw[i-]*i%Mod;
ny[]=ny[]=;
for(int i=;i<=n;i++) ny[i]=1LL*(Mod-Mod/i)*ny[Mod%i]%Mod;
for(int i=;i<=n;i++) ny[i]=1LL*ny[i]*ny[i-]%Mod;
memset(f,,sizeof(f));
f[][]=;
for(int i=;i<=n;i++)
for(int j=;j<=n;j++)
for(int k=;k<=a[i];k++)
{
if(k>j) break;
f[i][j]=(f[i][j]+1LL*f[i-][j-k]*c[a[i]][k]%Mod*pw[a[i]]%Mod*ny[a[i]-k]%Mod)%Mod;
}
for(int i=;i<=n;i++)
{
if(i&) ans-=1LL*f[n][i]*pw[n-i]%Mod;
else ans+=1LL*f[n][i]*pw[n-i]%Mod;
ans=(ans%Mod+Mod)%Mod;
}
for(int i=;i<=n;i++) if(a[i]) ans=(1LL*ans*ny[a[i]])%Mod;
printf("%d\n",ans);
return ;
}

2017-04-11 14:25:31