LOJ #6358 前夕 (组合计数、容斥原理)

题目链接

https://loj.ac/problem/6358

题意

题面写得就像一坨X一样,我来复述一下吧。
\(n\)个元素构成的集合,要从\(2^n\)个子集中选出若干个使得交的大小为\(4\)的倍数。不选算交为空。
样例解释: 选空集有\(8\)种方案,不选空集方案只有\(\{ 1\} \{ 2\}\)\(\{ 1\} \{ 2\} \{1,2\}\), 还有一种什么都不选,共\(11\)种。

题解

这道题真的神仙得令我目瞪口呆。。

首先考虑一个简单的容斥: 令\(F(k)\)表示钦定\(k\)个元素在所有选出的子集中必须出现,则\(F(k)={n\choose k}(2^{2^{n-k}}-1)\).
\(G(k)\)表示交集恰好为\(k\)的方案数,则有\(F(k)=\sum^{n}_{i=k} {k\choose i}G(k), G(k)=\sum^{n}_{i=k} (-1)^{i-k}{k\choose i}F(k)\).
那么要求的就是\(ans=\sum^n_{k\equiv 0(\mod 4)} G(k)\).

前方高能——
我们考虑构造一个系数\(\alpha(i)\) (官方题解将它称为“容斥系数”,可我并没有发现它和容斥原理有什么关系?) 使得\(ans=\sum^{n}_{i=0}F(i)\alpha(i)\).
如果没有\(k\)\(4\)的倍数这个条件,对所有\(k\)求和,那么根据容斥的式子可以推出来\(\alpha(i)=[i=0]\)即可达到目的。
现在有了这个条件,我们考虑刚才实际上我们在干什么:
对于一个\(G(n)\), 其在\(F(k)\)中会被计算\(n\choose k\)次,我们希望总共计算的次数是\(1\)次,那么也就是\[\forall n, \sum^{n}_{k=0} {n\choose k}\alpha(k)=1\], 取\(\alpha(k)=[k=0]\)即可. 现在我们就是要\(\forall n, \sum^{n}_{k=0} {n\choose k}\alpha(k)=[k\equiv 0(\mod 4)]\). 于是根据二项式反演有\(F(n)=\sum^{n}_{k=0} (-1)^{n-k}{n\choose k}[k\equiv 0(\mod 4)]\).

这个东西怎么快速求?掏出数论中走街串巷杀题越货之必备良品——单位根!令\(m=4\), \(\omega\)\(4\)次(主)单位根,则有\[[n\equiv 0(\mod m)]=\frac{1}{n}\sum^{m-1}_{i=0} \omega^{in}\]
于是\(\alpha(n)=\frac{1}{m}\sum^{n}_{k=0}(-1)^{n-k}{n\choose k}\sum^{m-1}_{i=0}(\omega^i)k=\sum^{m-1}_{i=0}(\omega^i-1)^k\)
直接计算即可。

时间复杂度\(O(nm)\).

启示: 最近连做了两道神仙构造的题,经常可以构造一些转移矩阵/容斥系数/递推式之类的东西以达到目的,这种思路值得借鉴。

代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#define llong long long
using namespace std;

inline int read()
{
    int x=0; bool f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
    for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
    if(f) return x;
    return -x;
}

const int N = 1e7;
const int P = 998244353;
const llong G = 3ll;
const llong W = 911660635ll;
const llong INV4 = 748683265ll;

int fact[N+3],finv[N+3];
llong f[N+3],a[N+3];
int n;

llong quickpow(llong x,llong y)
{
    llong cur = x,ret = 1ll;
    for(int i=0; y; i++)
    {
        if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}
        cur = cur*cur%P;
    }
    return ret;
}
llong comb(llong x,llong y) {return x<0||y<0||x<y ? 0ll : (llong)fact[x]*(llong)finv[y]%P*(llong)finv[x-y]%P;}

int main()
{
    fact[0] = 1ll; for(int i=1; i<=N; i++) fact[i] = (llong)fact[i-1]*i%P;
    finv[N] = quickpow(fact[N],P-2); for(int i=N-1; i>=0; i--) finv[i] = (llong)finv[i+1]*(i+1ll)%P;
    scanf("%d",&n);
    f[n] = 2ll; for(int i=n-1; i>=0; i--) f[i] = f[i+1]*f[i+1]%P;
    for(int i=0; i<=n; i++) f[i]--;
    for(int i=0; i<=n; i++) f[i] = f[i]*comb(n,i)%P;
    for(int i=0; i<4; i++)
    {
        llong tmp = 1ll,expn = quickpow(W,i);
        for(int j=0; j<=n; j++)
        {
            a[j] = (a[j]+tmp)%P;
            tmp = tmp*(expn-1ll)%P;
        }
    }
    for(int i=0; i<=n; i++) a[i] = a[i]*INV4%P;
//  for(int i=0; i<=n; i++) printf("%lld ",a[i]); puts("");
    llong ans = 1ll;
    for(int i=0; i<=n; i++) ans = (ans+f[i]*a[i])%P;
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/suncongbo/p/11275081.html