[BZOJ2839] 集合计数

Description

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

Solution

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

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

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

[f(k) = sum_{i=k}^n g(i)inom 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;
}

原文地址:https://www.cnblogs.com/mollnn/p/13191055.html