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)个元素的方案数之和,则有:
具体一点的话就是考虑构造一种这(k)个元素一定在交集中的方案,我们可以将这(k)个元素全部拎出来,然后剩下的(n-k)个元素组成若干个集合(不一定所有元素都要用完),然后再给每一个集合都加入这(k)个元素,这样交集一定满足要求,所以就是(n-k)个元素可以组成(2^{n-k})个集合,然后每个集合可以选或者不选,最后再(-1)将空集去掉,就是后面括号中的那一部分,前面的话是在(n)个数里面选(k)个数有多少种不同的选法
然后我们考虑构造一个容斥系数(f(x)),使得:
接下来就是怎么求这个(f)了
我们考虑对于一种交集大小恰好为(x)的方案,在设计中应该只有满足(x\% 4==0)的时候会计算一次,否则不会算,也就是([x mod 4=0])
然而实际上看回这条式子中我们计算的次数是:
具体一点的话就是,我们考虑这个最终的交集中,有多少个数是一开始钦定一定在交集中的(也就是枚举的(i)),然后因为这个方案满足(i)个数一定在交集中所以看回上面的式子就有(f(i))的贡献了,然后因为这个(i)还要具体到哪些数,所以还在再乘上一个组合数相当于枚举是哪(i)个
因此我们的(f(i))应该满足的条件是
接下来的化简需要用到二项式反演,经典形式有这两种:
证明的话(自己比较懒没有写相关博客qwq)可以看这里Portal-->
所以如果将([xequiv 0 (mod 4)])看成一个函数(g(x))的话,我们就可以把上面的式子写成:
这个时候,我们又要用到一个很神奇的东西:单位复数根(Portal -->FFT)
记(w_m)为主(m)次单位根,那么根据这个东西的求和引理当(m>=1)时我们可以的到:
当(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)):
第一步到第二步中((-1)^{x-i})变成了((-1)^x(-1)^i)是因为((-1)^i=(-1)^{-i})
第二步到第三步是将((-1)^x)提出来然后根据二项式定理可以得出这个东西是((1-w_m^j)^x),二项式定理的一般形式这里还是写一下:
中间是负号的话就是多乘一个((-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
*/