BZOJ4665 小 w 的喜糖 题解
BZOJ4665 小 w 的喜糖
这道题可以说是二项式反演的经典应用。
第一次转化,题目中说使每个人手里的糖都不相同,类似于错排问题,而我们显然是不好直接进行处理的。于是考虑转化为计算使一部分人手里的糖与原来相同的方案数,如果记作 \(g(i)\),那么答案就是 \(g(0)\)。
第二次转化,看到恰好,于是用二项式反演转化为求钦定。记钦定的方案为 \(f(i)\),则二项式反演的公式为
既然我们只需要求出 \(g(0)\),那么根据上述公式,就有
考虑如何求出 \(f(0),f(1),\dots,f(n)\)。不妨用 DP 来求解,设 \(\mathit{dp}(i,j)\) 表示枚举到第 \(i\) 种喜糖,钦定 \(j\) 个人拿到的糖和原来相同的方案数。发现同种喜糖是相同的,而这一限制比较麻烦,不妨认为所有喜糖都是互不相同的,而我们只需在最后给答案乘上 \(\prod1/c_i!\) 即可,其中 \(c_i\) 是第 \(i\) 种喜糖的数量。这样我们就暂时不用考虑去重问题,有转移方程
简单来说就是枚举在所有钦定的人中,有多少人的糖在当前种类里,即 \(k\)。那么我们首先要从原来就是这种糖的人中选出 \(k\) 个人,然后再挑出 \(k\) 个糖按顺序放在这些人手上。所以会先乘排列数然后乘组合数。
边界条件显然是 \(\mathit{dp}(0,0)=1\)。这样算下来之后,我们的 \(f(i)\) 实际上是 \(\mathit{dp}(m,i)\times(n-i)!\),因为 \(\mathit{dp}(i,j)\) 的转移方程里只计算了钦定的方案数,而没有计算钦定以外的人随意排列的方案数。
那么现在我们有了 \(f(i)\),就能算出 \(g(0)\),然后再乘上 \(\prod1/c_i!\),这个题就做完了。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
constexpr int MAXN=2005;
constexpr ll MOD=1e9+9;
int n,c[MAXN],m;
ll fac[MAXN],inv[MAXN];
ll f[MAXN][MAXN];
ll power(ll a,ll b){
ll res=1;
for(;b;a=a*a%MOD,b>>=1)if(b&1)res=res*a%MOD;
return res;
}
void init(int n){
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
inv[n]=power(fac[n],MOD-2);
for(int i=n-1;~i;i--) inv[i]=inv[i+1]*(i+1)%MOD;
}
ll C(int n,int m){
if(n<m) return 0;
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
ll A(int n,int m){
if(n<m) return 0;
return fac[n]*inv[n-m]%MOD;
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin>>n;
init(n);
for(int i=1,x;i<=n;i++) cin>>x,c[x]++,m=max(m,x);
f[0][0]=1;
for(int i=1,pre=c[1];i<=m;i++,pre+=c[i])
for(int j=0;j<=pre;j++)
for(int k=0;k<=min(j,c[i]);k++)
f[i][j]=(f[i][j]+f[i-1][j-k]*C(c[i],k)%MOD*A(c[i],k))%MOD;
ll ans=0;
for(int i=0;i<=n;i++) ans=(ans+f[m][i]*fac[n-i]*(i&1?-1:1))%MOD;
for(int i=1;i<=m;i++) ans=ans*inv[c[i]]%MOD;
cout<<(ans+MOD)%MOD<<'\n';
return 0;
}

浙公网安备 33010602011771号