[BZOJ2839] 集合计数 - 二项式反演,组合数学

Description

\(n\) 元集的幂集有多少个子集的广义交大小为 \(k\)

Solution

这里我们需要用到二项式反演

\[f(n)=\sum_{i=n}^m \binom i n g(i) \Leftrightarrow g(n)=\sum_{i=n}^m (-1)^{i-n} \binom i n f(i) \]

\(f(i)\) 表示交集元素至少有 \(i\) 个的方案数,设 \(g(i)\) 表示交集元素恰好为 \(i\) 个的方案数,于是有关系

\[f(k) = \sum_{i=k}^n g(i)\binom i k \]

根据二项式反演得

\[g(k)=\sum_{i=k}^n (-1)^{i-k} {i \choose k}f(i) \]

考虑 \(f(i)\),我们先钦定 \(i\) 个元素为交集元素,包含这 \(i\) 个元素的集合有 \(n-i\) 个,可以选择任意个,但不能一个不选,于是

\[f(i)={n \choose i} (2^{2^{n-i}}-1) \]

于是得

\[g(k)=\sum_{i=k}^n (-1)^{i-k} {i \choose k}{n \choose i} (2^{2^{n-i}}-1) \]

后面的指数项可以根据费马小定理利用快速幂暴力计算,

\(a_i={i \choose k}, b_i={n \choose i}\),则 \(a_k=1,b_n=1\),且由于

\[a_i=\frac {i!}{k!(i-k)!} = \frac {(i-1)!}{k!(i-k-1)!} \frac i {i-k}=a_{i-1} \frac i {i-k} \]

\[b_i=\frac{n!} {i!(n-i)!}=\frac {n!} {(i+1)!(n-i-1)!} \frac {i+1}{n-i}=b_{i+1} \frac {i+1}{n-i} \]

都可以 \(O(n)\) 预处理,于是总时间复杂度 \(O(n \log n )\)

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int mod = 1e9+7;

int qpow(int p,int q,int mod) {
    return (q&1?p:1) * (q?qpow(p*p%mod,q/2,mod):1) % mod;
}

int qpow(int p,int q) {
    return qpow(p,q,mod);
}

int inv(int p) {
    return qpow(p,mod-2);
}

const int N = 1000005;

int k,n,m,a[N],b[N];

void presolve() {
    a[k]=1;
    for(int i=k+1;i<=n;i++) a[i]=a[i-1]*i%mod*inv(i-k)%mod;
    b[n]=1;
    for(int i=n-1;i>=k;i--) b[i]=b[i+1]*(i+1)%mod*inv(n-i)%mod;
}

signed main() {
    ios::sync_with_stdio(false);
    cin>>n>>k;
    presolve();
    int ans=0;
    for(int i=k;i<=n;i++) {
        int tmp=a[i]*b[i]%mod*(qpow(2,qpow(2,n-i,mod-1))-1)%mod;
        if((i-k)&1) ans-=tmp;
        else ans+=tmp;
        ans=(ans%mod+mod)%mod;
    }
    cout<<ans;
}

猜你喜欢

转载自www.cnblogs.com/mollnn/p/13191055.html