https://ac.nowcoder.com/acm/contest/11168/D
给定 n 个小球,每个小球有颜色,要将它们摆成一行 。
两个方案不同,当且仅当存在某个位置,两种方案摆在这个位置的小球颜色不同。
一个方案合法, 当且仅当不存在任意两个位置相邻的小球颜色相同,求合法方案数对 10^9+7 取模后的值 。
首先考虑取反面计算。考虑"两个方案不同,当且仅当存在某个位置,两种方案摆在这个位置的小球颜色不同。"如何计算总方案数。
我们直接没有任何限制的就是fac[n],但是不能有两个位置颜色同的。我们用fac算的时候把B的两个颜色即B1,B2算成了不同的两个方案。实际上一样的。所以对于fac[n]里的所有方案数,B1,B2有fac[2](2X1)的总方案数的重复,所以总方案数就是(cnt表示颜色为2的个数) fac[n]/(2^cnt)
然后总共重复最多5e5.我们考虑容斥。记si为存在两个位置相邻的小球颜色相同。
ans=总数-| s1∪s2∪s3......∪scnt|
也就是减去s1,s2,s3...scnt,
加上s1∩s2,s1∩s3......
考虑怎么求只有一对颜色不同的。首先一对有C(m,1)种选法。既然条件是相邻位置,我们把这对颜色同的小球捆绑。那么此时剩下的小球有n-i个,全排列是fac[n-i],那么此时还要考虑其他颜色的满足第一个条件的状态,fac[n-i]/(2^(cnt-1) 【和最开始的处理合法总数一样】
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<unordered_map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e6+100;
typedef long long LL;
const LL mod=1e9+7;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
LL fac[maxn],inv[maxn],inv1[maxn];
LL a[maxn];
unordered_map<LL,LL>map1;
LL ksm(LL a,LL k){
LL res=1;
while(k>0){
if(k&1) res=res*a%mod;
k>>=1;
a=a*a%mod;
}
return res%mod;
}
void init(){
fac[0]=1;
for(LL i=1;i<maxn-10;i++) fac[i]=fac[i-1]*i%mod;
inv[0]=1;
for(LL i=1;i<maxn-10;i++) inv[i]=inv[i-1]*ksm((LL)i,mod-2)%mod;
inv1[0]=1;
for(LL i=1;i<maxn-10;i++) inv1[i]=inv1[i-1]*ksm((LL)2,mod-2)%mod;
}
LL C(LL n,LL m){
return (fac[m]%mod*inv[n]%mod*inv[m-n]%mod)%mod;
}
int main(void)
{
init();
LL n;n=read();
LL cnt=0;
for(LL i=1;i<=n;i++){
a[i]=read();
if(map1.count(a[i])){
cnt++;
}
else map1[a[i]]=1;
}
LL ans=(fac[n]%mod*inv1[cnt]+mod)%mod;
for(LL i=1;i<=cnt;i++){
if(i&1){
ans=(ans%mod-C(i,cnt)%mod*fac[n-i]%mod*inv1[cnt-i]%mod+mod)%mod;
}
else{
ans=(ans%mod+C(i,cnt)%mod*fac[n-i]%mod*inv1[cnt-i]%mod+mod)%mod;
}
ans=(ans+mod)%mod;
}
printf("%lld\n",ans);
return 0;
}