容斥定理

容斥定理

其实容斥很早就学过了,原理很简单,公式也是,但是运用起来却发现异常困难。
在网上查阅了很多资料,感觉容斥还是偏运用吧,这里就整理一些题目。

具有性质\(A\)或者\(B\)的元素个数,等于具有性质\(A\)或者\(B\)的元素个数的和,减去具有性质\(A\)\(B\)的元素的个数,使得计算的结果无重无漏。
先把公式写在这:
\(|A_1 \cup A_2\cup...A_m| = \sum\limits_{1\leq i \leq m}|A_i|-\sum\limits_{1\leq i <j\leq m}|A_i\cap A_j|+\sum\limits_{1\leq i <j<k\leq m}|A_i\cap A_j\cap A_k|-...+(-1)^{m-1}|A_1\cap A_2\cap ... \cap A_m|\)

错位排序

错位排序的\(DP\)推理方式在我之前的博客已经提到过,现在尝试用容斥的思路进行推导。

假设集合\(S\)\(1,2,...,n\)的所有全排列,\(|S|=n!\)。定义\(S_i\)为数字\(i\)排在\(i\)位置上的全排列,因为数字\(i\)不动,所以
\(|S_i| = (n-1)!【i = 1,2,....,n】\)
依次类推可以得到固定\(k\)个位置时的全排列个数,\((n-k)!\)

现在我们定义\(D_n\)为每个元素都不在自己位置上的排列数。

由容斥定理:
\(D_n = |S|-\sum\limits_{1\leq i \leq n}|S_i|+\sum\limits_{1\leq i <j\leq n}|S_i\cap S_j|+...+(-1)^{n-1}|S_1\cap S_2\cap...\cap S_n|\\=n!(1-\frac{1}{1!}+\frac{1}{2!}-...\pm\frac{1}{n!})\)

欧拉函数

\(\varphi(n)\)表示为\([1,n]\)中与\(n\)互质的数。
特别的:\(p\)为素数时\(\varphi(p) = p-1\)
并且\(gcd(a,b)== 1\)时,\(\varphi(ab)=\varphi(a)\varphi(b)\)
下面用容斥求一下欧拉函数:
\(N\)分解为素数的乘积,\(N = p_1^{r_1}p_2^{r_2}...p_k^{r_k}\)
\(A_i\)表示\(1\)\(N\)\(p_i\)倍数的集合 ,有\(|A_i| = \lfloor{\frac{N}{p_i}}\rfloor\)
对于\(p_i \neq p_j\),
\(|A_i \cap A_j| = \lfloor \frac{N}{p_ip_j}\rfloor\)
依次类推,可以得到若干个数相乘的集合,
最后用容斥得到公式(这个公式好像挺难推的)
\(\varphi(N) = N(1-\frac{1}{p_1})(1-\frac{1}{p_2})...(1-\frac{1}{p_k})\)

给两个求欧拉函数的板子:

//基于素因数分解求欧拉函数的算法
ll get_phi(ll x){
    ll res = x;
    for(ll i = 2;i*i <= x;i++){
        if(x%i == 0){
            res = res/i*(i-1);
            while(x%i == 0) x/=i;
        }
    }
    if(x!=1) res = res/x*(x-1);
    return res;
}
//利用埃式筛,实现预处理
ll phi[N];
void init(){
    for(ll i = 0;i < N;i++) phi[i] = i;
    for(ll i = 2;i < N;i++){
        if(phi[i] == i){
            for(ll j = i;j < N;j+=i){
                phi[j] = phi[j]/i*(i-1);
            }
        }
    }
}

2018hdu多校 A
假如没有限制,我们不难得出\(k\)拆分成\(m\)个的方案数为\(C_{m+k-1}^{m-1}\)
于是开始考虑限制,假设\(A_i\)表示第\(i\)个数超出限制时所产生的集合。那么有\(j\)个的数超出限制的方案数为任选\(j\)个这样的集合。我们发现这恰好符合就是容斥的第\(j\)阶,超出的怎么算?可以把做两边同时减去\(n*j\),得到\(C_{m+k-1-n*j}^{m-1}\)于是第\(j\)阶的就是\((-1)^{j}C_{m}^{j}C_{m+k-1-n*j}^{m-1}\)。最后求和即可。

还有一种生成函数的做法。问题等价于\((1+x^1+x^2+x^3+...+x^{n-1})^m\)\(x^k\)的系数,里面等比数列求和,\(({1-x^n})^m{(1-x})^{-m}\),前半部分二项式展开,后半部分泰勒展开,最后求和找系数,最后的公式是一样的。

ll n,m,k;
ll fac[N],inv[N];
ll f_pow(ll a,ll b){
    ll res = 1;
    while(b){
        if(b&1) res = res%mod*a%mod%mod;
        a = a%mod*a%mod%mod;
        b>>=1;
    }
    return res%mod;
}
void init(){
    inv[0] = fac[0] = 1;
    for(ll i = 1; i < N;i++)fac[i] = fac[i-1]*i%mod;
    inv[N-1] = f_pow(fac[N-1],mod-2);
    for(ll i = N-2;i > 0;i--) inv[i] = inv[i+1]*(i+1)%mod;
}
ll C(ll a,ll b){
    if(a > b || a < 0 || b < 0) return 0;
    if(a == 0 || a == b) return 1;
    return fac[b]*inv[a]%mod*inv[b-a]%mod;
}
void solve(){
    scanf("%lld%lld%lld",&n,&m,&k);
    ll ans = 0;
    for(ll i = 0;i*n <= k && i <= m;i++){
        ll tmp = C(i,m)*C(m-1,m-1+k-n*i)%mod;
        //cout<<C(i,m)<<' '<<C(m-1,m-1+k-n*i)<<' '<<tmp<<endl;
        if(i&1) ans = (ans-tmp+mod)%mod;
        else ans = (ans+tmp)%mod;
        //cout<<tmp<<' '<<ans<<endl;
    }
    printf("%lld\n",ans);
}
int main(){
    init();
    //for(ll a,b;cin>>a>>b;cout<<C(a,b)<<endl){}
    ll t;scanf("%lld",&t);
    while(t--)solve();
}

P6298
看到有人用莫比乌斯反演orz
题解

p6692

题解

posted @ 2021-09-18 18:28  Paranoid5  阅读(434)  评论(0)    收藏  举报