1. 程式人生 > 實用技巧 >習題:Iahub and Permutations(容斥)

習題:Iahub and Permutations(容斥)

題目

傳送門

思路

這裡的解法的說明應該是可以拓展的

首先簡化問題,有\(n\)個盒子,\(n\)個球,盒子有編號,球也有編號,只有\(cnt\)個編號盒子中有,並且球中也有這些編號

正難則反,總共的方案數就是\(n!\),要算的可以轉換為不合法的方案

即總共的方案可以轉換為恰好有一個盒子不滿足一直累加到恰好有\(cnt\)盒子不滿足

按照套路,將恰好有\(i\)個盒子不滿足轉換成為至少有\(i\)個盒子不滿足

那麼考慮如果有一個方案,它有\(t\)個盒子不滿足,那麼這個方案會被哪些條件統計到?

\(f(i)\)為容斥係數,那麼一定滿足

\(1=\sum_{i=1}^{t}C_t^if(i)\)

那麼轉換為求\(f\)的值

如果\(f(t)\)已知,嘗試推到到\(f(t+1)\)

\(1=\sum_{i=1}^{t+1}C_{t+1}^if(i)=f(t+1)+\sum_{i=1}^{t}C_{t+1}^if(i)\)

那麼有\(f(t+1)=1-\sum_{i=1}^{t}C_{t+1}^if(i)\)

那麼接下來的工作就很簡單了,容斥係數已經求出,剩下的只剩統計至少\(i\)個的方案數了

很簡單,即為\(C_{cnt}^i(n-i)!\)

程式碼

#include<iostream>
using namespace std;
const int mod=1e9+7;
int n;
int a[2005],f[2005];
int tot,cnt;
long long ans;
long long fac[2005],inv[2005];
bool vis[2005],used[2005];
long long c(int n,int m)
{
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
long long qkpow(int a,int b)
{
    if(b==0)
        return 1;
    if(b==1)
        return a;
    long long t=qkpow(a,b/2);
    t=t*t%mod;
    if(b&1)
        t=t*a%mod;
    return t;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    fac[0]=1;
    inv[0]=1;
    for(int i=1;i<=n;i++)
    {
        fac[i]=i*fac[i-1]%mod;
        inv[i]=qkpow(fac[i],mod-2);
    }
    for(int i=1;i<=n;i++)
        if(a[i]==-1)
        {
            vis[i]=1;
            tot++;
        }
    for(int i=1;i<=n;i++)
        if(a[i]!=-1)
            used[a[i]]=1;
    for(int i=1;i<=n;i++)
        if(vis[i]==1&&used[i]==0)
            cnt++;
    f[1]=1;
    for(int i=2;i<=cnt;i++)
    {
        long long temp=0;
        for(int j=1;j<i;j++)
            temp=(temp+c(i,j)*f[j]%mod)%mod;
        f[i]=(1+mod-temp)%mod;
    }
    for(int i=1;i<=cnt;i++)
    {
        ans=(ans+f[i]*c(cnt,i)%mod*fac[tot-i]%mod)%mod;
    }
    cout<<((fac[tot]-ans)%mod+mod)%mod;
    return 0;
}