zoukankan      html  css  js  c++  java
  • 前夕

    Portal -->broken qwq

    Description

      为了抵御以尼古拉奥尔丁为首的上古龙族的入侵,地球的守护者 Yopilla 集齐了$ n$ 种人类文明的本源力量 —— 世界之力。

    Yopilla 打算使用若干种技能来对抗尼古拉奥尔丁的进攻。每种技能由若干种世界之力构成。换句话说,一共有 $2 ^ n $种技能,Yopilla 要使用若干种技能来对抗尼古拉奥尔丁。

    大战前夕,Yopilla 走在波士顿的街头,突然看见天空中飞过了 (4) 只白鸽,他便洞察了战胜尼古拉奥尔丁的秘密:只要他使用的技能中,都含有的世界之力的种类数恰好为 (4) 的倍数,他便可以打败敌人。

    Yopilla 想知道,他有多少种使用技能的情况,能战胜敌人。请你替 Yopilla 回答这个问题,答案对 (998244353) 取模即可。

      数据范围:(1<=n<=10^7)
      

    Solution

    ​  其实就是说要从(2^n)个集合中选一些出来使得交集大小为(4)的倍数的方案数

    ​  (首先要疯狂orzypl真的学长太强啦qwq感觉也是。。不看题解怎么都想不出来系列qwq)

      (套路预警

      我们定义一个函数(a(x))表示交集中一定含有某(k)个元素的方案数之和,则有:

    [a(x)=inom n k (2^{2^{n-k}}-1) ]

      具体一点的话就是考虑构造一种这(k)个元素一定在交集中的方案,我们可以将这(k)个元素全部拎出来,然后剩下的(n-k)个元素组成若干个集合(不一定所有元素都要用完),然后再给每一个集合都加入这(k)个元素,这样交集一定满足要求,所以就是(n-k)个元素可以组成(2^{n-k})个集合,然后每个集合可以选或者不选,最后再(-1)将空集去掉,就是后面括号中的那一部分,前面的话是在(n)个数里面选(k)个数有多少种不同的选法

      然后我们考虑构造一个容斥系数(f(x)),使得:

    [ans=sumlimits_{i=0}^{n}f(i)a(i) ]

      接下来就是怎么求这个(f)

      我们考虑对于一种交集大小恰好为(x)的方案,在设计中应该只有满足(x\% 4==0)的时候会计算一次,否则不会算,也就是([x mod 4=0])

      然而实际上看回这条式子中我们计算的次数是:

    [sumlimits_{i=0}^{x}inom x i f(i) ]

      具体一点的话就是,我们考虑这个最终的交集中,有多少个数是一开始钦定一定在交集中的(也就是枚举的(i)),然后因为这个方案满足(i)个数一定在交集中所以看回上面的式子就有(f(i))的贡献了,然后因为这个(i)还要具体到哪些数,所以还在再乘上一个组合数相当于枚举是哪(i)

      因此我们的(f(i))应该满足的条件是

    [[xequiv 0 (mod 4)]=sumlimits_{i=0}^{x}inom x i f(i) ]

      接下来的化简需要用到二项式反演,经典形式有这两种:

    [egin{aligned} f_n=sumlimits_{i=0}^n (-1)^{i}inom n i g_i &Leftrightarrow g_n=sumlimits_{i=0}^{n}(-1)^iinom n i f_i\ f_n=sumlimits_{i=0}^{n}inom n i g_i&Leftrightarrow g_n=sumlimits_{i=0}^{n}(-1)^{n-i}inom n i f_i end{aligned} ]

    ​  证明的话(自己比较懒没有写相关博客qwq)可以看这里Portal-->

    ​  所以如果将([xequiv 0 (mod 4)])看成一个函数(g(x))的话,我们就可以把上面的式子写成:

    [f(i)=sumlimits_{i=0}^{x}(-1)^{x-i}inom x i [iequiv 0 (mod 4)] ]

    ​  这个时候,我们又要用到一个很神奇的东西:单位复数根(Portal -->FFT)

    ​  记(w_m)为主(m)次单位根,那么根据这个东西的求和引理当(m>=1)时我们可以的到:

    [sumlimits_{i=0}^{m-1}(w_m^k)^i= egin{cases} 0& (k\%m!=0)\ m& (k\%m==0)\ end{cases} ]

    ​  当(k\%m!=0)的时候就是求和引理本身的内容,而当(k\%m==0)的时候(w_m^k=w_m^0=1),所以求和之后就是(m)了(证明什么的在FFT那篇博里面qwq)

    ​  所以([iequiv 0 (mod 4)])可以看成令(m=4)时的(frac{1}{m}sumlimits_{i=0}^{m-1}(w_m^k)^i)

    ​  所以我们上面的式子又可以进一步写成(其中(m=4)):

    [egin{aligned} f(i)&=sumlimits_{i=0}^{x}(-1)^{x-i}inom x ifrac{1}{m}sumlimits_{j=0}^{m-1}(w_m^i)^j\ &=frac{1}{m}sumlimits_{j=0}^{m-1}sumlimits_{i=0}^{x}(-1)^x[(-1)^iinom x i (w_m^j)^i]\ &=frac{(-1)^x}{m}sumlimits_{j=0}^{m-1}(1-w_m^j)^x end{aligned} ]

      第一步到第二步中((-1)^{x-i})变成了((-1)^x(-1)^i)是因为((-1)^i=(-1)^{-i})

    ​  第二步到第三步是将((-1)^x)提出来然后根据二项式定理可以得出这个东西是((1-w_m^j)^x),二项式定理的一般形式这里还是写一下:

    [(x+y)^n=sumlimits_{k=0}^{n}inom n {k} x^{k}y^{n-k} ]

    ​  中间是负号的话就是多乘一个((-1)^k)

    ​  

    ​  于是我们就可以在(O(nm))的时间内求出(f)然后再暴力用(f)(a)求出答案就好了(这里的(m)其实就是(4)。。)

    ​  单位复数根的话可以用模数的原根来求(跟NTT一样的)

    ​  然后还有一个小细节就是在求(a)的时候(2^{2^{n-k}})这步如果直接处理显然会炸,所以我们可以考虑枚举(k)的时候倒过来枚举,然后把这个数记在一个变量里,每次直接这个变量平方一下就可以得到下一次的答案了(具体自己写写指数就知道了。。(k)减小(1)的时候(n-k)会增加(1),那(2^{n-k})就会是原来的两倍,所以直接平方一下就好了)

      

      代码大概长这个样子

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define ll long long
    using namespace std;
    const int N=1e7+10,MOD=998244353,inv4=748683265;
    int fac[N],invfac[N];
    int f[N],w[4],W[4];
    int n,m,tmp;
    int ksm(int x,ll y){
    	int ret=1,base=x;
    	for (;y;y>>=1,base=1LL*base*base%MOD)
    		if (y&1) ret=1LL*ret*base%MOD;
    	return ret;
    }
    void prework(int n){
    	fac[0]=1;
    	for (int i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%MOD;
    	invfac[n]=ksm(fac[n],MOD-2);
    	for (int i=n-1;i>=0;--i) invfac[i]=1LL*invfac[i+1]*(i+1)%MOD;
    	w[0]=1; w[1]=ksm(3,(MOD-1)/4);
    	for (int i=2;i<4;++i) w[i]=1LL*w[i-1]*w[1]%MOD;
    	for (int i=0;i<4;++i) W[i]=1,w[i]=(1LL+MOD-w[i])%MOD;
    }
    int C(int n,int m){return n<m?0:1LL*fac[n]*invfac[m]%MOD*invfac[n-m]%MOD;}
    void solve(){
    	int ret=1,tmp,mi=2;
    	for (int i=n;i>=0;--i){
    		tmp=1LL*C(n,i)*((mi-1+MOD)%MOD)%MOD;
    		ret=(1LL*tmp*f[i]%MOD+ret)%MOD;
    		mi=1LL*mi*mi%MOD;
    	}
    	printf("%d
    ",ret);
    }
    
    int main(){
    #ifndef ONLINE_JUDGE
    	freopen("a.in","r",stdin);
    #endif
    	scanf("%d",&n);
    	prework(n);
    	int mark=-1;
    	for (int i=0;i<=n;++i){
    		mark*=-1;
    		f[i]=0;
    		for (int j=0;j<4;++j)
    			f[i]=(1LL*f[i]+W[j])%MOD;
    		f[i]=1LL*mark*f[i]*inv4%MOD;
    		if (f[i]<0) f[i]+=MOD;
    		for (int j=0;j<4;++j) W[j]=1LL*W[j]*w[j]%MOD;
    	}
    	solve();
    }
    /*
    input
    2
    output
    11
    */
    
  • 相关阅读:
    测 试 报 告模板
    浅谈如何设计自动化测试框架
    GET与POST类型接口
    测试用例设计总结
    Java OO知识总结
    Java基础知识总结
    MIT 6.824学习笔记3 Go语言并发解析
    Go语言_并发
    MIT 6.824学习笔记4 Lab1
    Leetcode Lect3 二分法总结
  • 原文地址:https://www.cnblogs.com/yoyoball/p/9392428.html
Copyright © 2011-2022 走看看