牛客练习赛78 D CCA的小球
https://ac.nowcoder.com/acm/contest/11168/D
借助这个公式:
有重复集合的排列:
定理:设S是多重集合,他有k种不同类型的对象,每一种类型的有限重复数是n1,n2,n3,…nk。设S的大小为n=n1+n2+n3+…nk。则S的n排列数目为n!/(n1!n2!n3!…nk!)
用容斥原理
f(i)表示至少有i对颜色相同的球相邻的方案数
那么 答案=f(0)-f(1)+f(2)-f(3)……
如何求f(i)
假设一共有c2对球颜色相同,c1个颜色唯一的球,c1=n-2*c2
要求至少有i对颜色相同的球相邻,就可以把每对看做1个球
所以相当于有c2-i对颜色相同的球,c1+i个颜色唯一的球,球的总数为2*(c2-i)+c1+i
根据有重复集合的排列公式 f(i)=C(c2,i) * (2*(c2-i)+c1+i)! / [2^(c2-i)]
组合数和逆元要递推,不然会超时
之前一直考虑先固定好颜色唯一的球或者颜色相同的球,再往里面差另一种,是不行的
因为可以插入颜色相同球相邻,后面通过插别的隔开他们
#include<map> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int mod=1e9+7; #define N 1000001 int a[N]; int fac[N],inv[N]; int pow(int a,int b) { int c=1; for(;b;a=1ll*a*a%mod,b>>=1) if(b&1) c=1ll*c*a%mod; return c; } int main() { int n,x,c1=0,c2=0; scanf("%d",&n); if(!n) { printf("0"); return 0; } for(int i=1;i<=n;++i) scanf("%d",&a[i]); sort(a+1,a+n+1); for(int i=1;i<n;++i) if(a[i]==a[i+1]) c2++; c1=n-c2*2; fac[0]=1; for(int i=1;i<=n;++i) fac[i]=1ll*fac[i-1]*i%mod; inv[1]=1; for(int i=2;i<=c2;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod; int inv2=pow(2,mod-2); int in=pow(inv2,c2); int ans=1ll*fac[n]%mod*in%mod; int t=-1,c=1,tmp; for(int i=1;i<=c2;++i) { c=1ll*c*inv[i]%mod*(c2-i+1)%mod; in=2ll*in%mod; tmp=(1ll*c*fac[2*(c2-i)+c1+i]%mod*in%mod*t+mod)%mod; ans=(ans+tmp)%mod; t=-t; } printf("%d",ans); }