前夕

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)=\binom n k (2^{2^{n-k}}-1) \]

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

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

\[ans=\sum\limits_{i=0}^{n}f(i)a(i) \]

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

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

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

\[\sum\limits_{i=0}^{x}\binom x i f(i) \]

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

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

\[[x\equiv 0\ (mod\ 4)]=\sum\limits_{i=0}^{x}\binom x i f(i) \]

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

\[\begin{aligned} f_n=\sum\limits_{i=0}^n (-1)^{i}\binom n i g_i &\Leftrightarrow g_n=\sum\limits_{i=0}^{n}(-1)^i\binom n i f_i\\ f_n=\sum\limits_{i=0}^{n}\binom n i g_i&\Leftrightarrow g_n=\sum\limits_{i=0}^{n}(-1)^{n-i}\binom n i f_i \end{aligned} \]

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

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

\[f(i)=\sum\limits_{i=0}^{x}(-1)^{x-i}\binom x i [i\equiv 0\ (mod\ 4)] \]

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

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

\[\sum\limits_{i=0}^{m-1}(w_m^k)^i= \begin{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)

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

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

\[\begin{aligned} f(i)&=\sum\limits_{i=0}^{x}(-1)^{x-i}\binom x i\frac{1}{m}\sum\limits_{j=0}^{m-1}(w_m^i)^j\\ &=\frac{1}{m}\sum\limits_{j=0}^{m-1}\sum\limits_{i=0}^{x}(-1)^x[(-1)^i\binom x i (w_m^j)^i]\\ &=\frac{(-1)^x}{m}\sum\limits_{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=\sum\limits_{k=0}^{n}\binom 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\n",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
*/
posted @ 2018-07-30 19:57  yoyoball  阅读(250)  评论(0编辑  收藏  举报