6467. 【GDOI2020模拟02.09】西行寺无余涅槃

题目

思考历程

显然这题是道FWT。
按照我非常浅的理解,我只想到了使用FWT的最暴力的做法。
就一直想不到复杂度跟 k k k有关的。


正解

这题是比赛三题中思想最难,但实现最简单的题目。
首先讲讲那粗暴至极的思路:
对于每一行,粗暴地建立一个多项式(请允许我这么叫),然后做FWT异或卷积。

正解是在这个基础上进行优化。
首先,基本操作,将 p i , j p_{i,j} pi,j异或上 p i , 0 p_{i,0} pi,0,把它当作不选时就是选了 a 0 a_0 a0
可以发现,在FWT过后,只有 2 k − 1 2^{k-1} 2k1种取值。(此时行的大小为 2 m 2^m 2m
接着对每一列(指的是 n n n排多项式中的一列,就是 2 m 2^m 2m列中的一列)单独考虑,
如果是暴力,就直接将FWT后这些位置上的数都乘起来。
但现在,我们统计对于 2 k − 1 2^{k-1} 2k1种取值,每一种取值出现的次数。最后乘起来的时候一起乘。
假如 k = 3 k=3 k=3,那么这些取值分别是:
a 0 + a 1 + a 2 , a 0 − a 1 + a 2 , a 0 + a 1 − a 2 , a 0 − a 1 − a 2 a_0+a_1+a_2,a_0-a_1+a_2,a_0+a_1-a_2,a_0-a_1-a_2 a0+a1+a2,a0a1+a2,a0+a1a2,a0a1a2
记这四种取值的出现次数分别为 x 0 , x 1 , x 2 , x 3 x_0,x_1,x_2,x_3 x0,x1,x2,x3
− - 看成 1 1 1,把 + + +看成 0 0 0,可以通过 a 1 a_1 a1 a 2 a_2 a2的符号来计算出 x x x的下标。
现在我们要将这四个东西都求出来。
第一个限制: x 0 + x 1 + x 2 + x 3 = n x_0+x_1+x_2+x_3=n x0+x1+x2+x3=n,原因不解释。

先对每个 a a a单独考虑。以 a 1 a_1 a1举例子:
建立一个多项式,对于 i ∈ [ 1 , n ] i\in [1,n] i[1,n],在 p i , 1 p_{i,1} pi,1的位置加一。
对这个多项式做一遍FWT,做完之后就对于每一项,它的值就是它对应的列的 x 0 − x 1 + x 2 − x 3 x_0-x_1+x_2-x_3 x0x1+x2x3的值。
要解释这个,先说说异或FWT的实质: F W T ( F ) i = ∑ j = 0 L − 1 ( − 1 ) ∣ i ⋂ j ∣ F j FWT(F)_i=\sum_{j=0}^{L-1}(-1)^{|i\bigcap j|}F_j FWT(F)i=j=0L1(1)ijFj
∣ U ∣ |U| U表示的是 U U U 1 1 1的个数。
推一下式子: F W T ( F ) i = ∑ j = 0 2 m − 1 ( − 1 ) ∣ i ⋂ j ∣ ∑ k = 0 n − 1 ∣ p k , 1 = j ∣ FWT(F)_i=\sum_{j=0}^{2^m-1}(-1)^{|i\bigcap j|}\sum_{k=0}^{n-1}|p_{k,1}=j| FWT(F)i=j=02m1(1)ijk=0n1pk,1=j
F W T ( F ) i = ∑ k = 0 n − 1 ( − 1 ) ∣ i ⋂ p k , 1 ∣ FWT(F)_i=\sum_{k=0}^{n-1}(-1)^{|i\bigcap p_{k,1}|} FWT(F)i=k=0n1(1)ipk,1
对照一下FWT的实质,可以发现,对于第 i i i列而言,这就是所有行对它的贡献之和。( F W T ( A + B ) = F W T ( A ) + F W T ( B ) FWT(A+B)=FWT(A)+FWT(B) FWT(A+B)=FWT(A)+FWT(B)所以贡献是可以合在一起计算的)
而这些贡献是有正有负的。这个东西就可以理解为:正贡献的个数减去负贡献的个数。
再看看 x x x x 0 x_0 x0 x 2 x_2 x2 a 1 a_1 a1是正贡献, x 1 x_1 x1 x 3 x_3 x3 a 1 a_1 a1是负贡献,跟上面所述符合。
类似地,对 a 2 a_2 a2考虑,可以求出 x 0 + x 1 − x 2 − x 3 x_0+x_1-x_2-x_3 x0+x1x2x3

但是现在还缺一条方程。
p k , 1 ⨁ p k , 2 p_{k,1} \bigoplus p_{k,2} pk,1pk,2带进去, F W T ( F ) i = ∑ k = 0 n − 1 ( − 1 ) ∣ i ⋂ ( p k , 1 ⨁ p k , 2 ) ∣ FWT(F)_i=\sum_{k=0}^{n-1}(-1)^{|i\bigcap (p_{k,1} \bigoplus p_{k,2})|} FWT(F)i=k=0n1(1)i(pk,1pk,2)
与运算满足分配律: a ⋂ ( b ⨁ c ) = ( a ⋂ b ) ⨁ ( a ⋂ c ) a \bigcap(b \bigoplus c)=(a\bigcap b)\bigoplus (a \bigcap c) a(bc)=(ab)(ac)
于是我们就明白了:这个东西相当于 a 1 a_1 a1 a 2 a_2 a2贡献符号相同的个数减去贡献符号不同的个数。在这里就是 x 0 − x 1 − x 2 + x 3 x_0-x_1-x_2+x_3 x0x1x2+x3
推广一下,将正贡献记作 0 0 0,负贡献记作 1 1 1,那几个东西异或起来,求出的东西是异或为 0 0 0的个数减去异或为 1 1 1的个数。

这样我们就可以凑出 2 k − 1 2^{k-1} 2k1条方程,可以求解了。
但是不能直接暴力解。
观察一下:
x 0 + x 1 + x 2 + x 3 x_0+x_1+x_2+x_3 x0+x1+x2+x3
x 0 − x 1 + x 2 − x 3 x_0-x_1+x_2-x_3 x0x1+x2x3
x 0 + x 1 − x 2 − x 3 x_0+x_1-x_2-x_3 x0+x1x2x3
x 0 − x 1 − x 2 + x 3 x_0-x_1-x_2+x_3 x0x1x2+x3
如果对FWT有点了解,就会发现这不就是 F W T ( { x 0 , x 1 , x 2 , x 3 } ) FWT(\{x_0,x_1,x_2,x_3\}) FWT({x0,x1,x2,x3})嘛!
所以做一遍IFWT,就可以解方程了。
后面的操作就不用说了吧。
时间复杂度: O ( 2 k ( n + m ∗ 2 m ) ) O({2^k }(n+m*2^m)) O(2k(n+m2m))


代码

using namespace std;
#include 
#include 
#include 
#define N 1000010
#define MK 20
#define mo 998244353
#define ll long long
inline ll qpow(ll x,ll y=mo-2){
//	printf("%lld\n",y);
	ll res=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			res=res*x%mo;
	return res;
}
int n,m,K;
int a[MK];
int p[N][MK];
ll space[1<<MK],point=0;
ll *F[1<<MK],G[1<<MK],A[1<<MK],ans[1<<MK];
inline void FWT(ll A[],int n){
	for (int i=1;i<n;i<<=1)
		for (int j=0;j<n;j+=i<<1)
			for (int k=j;k<j+i;++k){
				ll x=A[k],y=A[k+i];
				A[k]=(x+y)%mo;
				A[k+i]=(x-y+mo)%mo;
			}
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	freopen("yuyuko.in","r",stdin);
	freopen("yuyuko.out","w",stdout);
	scanf("%d%d%d",&n,&m,&K);
	for (int i=0;i<K;++i)
		scanf("%d",&a[i]);
	int offset=0;
	for (int i=1;i<=n;++i){
		for (int j=0;j<K;++j)
			scanf("%d",&p[i][j]);
		offset^=p[i][0];
		for (int j=1;j<K;++j)
			p[i][j]^=p[i][0];
		p[i][0]=0;
	}
	F[0]=space+point;
	point+=1<<m;
	for (int T=0;T<1<<m;++T)
		F[0][T]=n;
	for (int S=1;S<(1<<K-1);++S){
		F[S]=space+point;
		point+=1<<m;
		for (int i=1;i<=n;++i){
			int x=0;
			for (int j=1;j<K;++j)
				if (S>>j-1&1)
					x^=p[i][j];
			F[S][x]++;
		}
		FWT(F[S],1<<m);
	}
	for (int S=0;S<(1<<K-1);++S){
		A[S]=a[0];
		for (int j=1;j<K;++j)
			if (S>>j-1&1)
				A[S]-=a[j];
			else
				A[S]+=a[j];
		A[S]=(A[S]%mo+mo)%mo;
	}
	for (int T=0;T<1<<m;++T){
		for (int S=0;S<(1<<K-1);++S)
			G[S]=F[S][T];
		FWT(G,1<<K-1);
		ans[T]=1;
		ll inv=qpow(1<<K-1);
		for (int j=0;j<(1<<K-1);++j)
			(ans[T]*=qpow(A[j],G[j]*inv%mo))%=mo;
	}
	FWT(ans,1<<m);
	ll inv=qpow(1<<m);
	for (int T=0;T<1<<m;++T)
		printf("%lld ",ans[T^offset]*inv%mo);
	return 0;
}

总结

FFT只会打板就算了,FWT千万不能只会打板啊!

你可能感兴趣的:(FWT)