zoukankan      html  css  js  c++  java
  • LOJ #6358 前夕 (组合计数、容斥原理)

    题目链接

    https://loj.ac/problem/6358
    (另外一道(4)的倍数题左转loj #6356)

    题意

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

    题解

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

    首先考虑一个简单的容斥: 令(F(k))表示钦定(k)个元素在所有选出的子集中必须出现,则(F(k)={Nchoose k}(2^{2^{N-k}}-1)).
    (G(k))表示交集恰好为(k)的方案数,则有(F(k)=sum^{N}_{i=k} {ichoose k}G(i), G(k)=sum^{N}_{i=k} (-1)^{i-k}{ichoose k}F(i)).
    那么要求的就是(ans=sum^N_{kequiv 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))中会被计算(nchoose k)次,我们希望总共计算的次数是(1)次,那么也就是$$forall n, sum^{n}_{k=0} {nchoose k}alpha(k)=1$$, 取(alpha(k)=[k=0])即可. 现在我们就是要(forall n, sum^{n}_{k=0} {nchoose k}alpha(k)=[kequiv 0(mod 4)]). 于是根据二项式反演有(alpha(n)=sum^{n}_{k=0} (-1)^{n-k}{nchoose k}[kequiv 0(mod 4)]).

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

    时间复杂度(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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    奇异值分解(SVD)与在降维中的应用
    拉格朗日乘子法&KKT条件
    有监督学习、无监督学习、半监督学习
    LSTM学习—Long Short Term Memory networks
    Ubuntu16.04+CUDA8.0+cuDNN5.1+Python2.7+TensorFlow1.2.0环境搭建
    激活函数
    C基础学习笔记
    Hive
    Fragment跳转至Activity片段随笔
    冒泡排序和选择排序
  • 原文地址:https://www.cnblogs.com/suncongbo/p/11275081.html
Copyright © 2011-2022 走看看