6467. 【GDOI2020模擬02.09】西行寺無余涅槃


題目

思考歷程

顯然這題是道FWT。
按照我非常淺的理解,我只想到了使用FWT的最暴力的做法。
就一直想不到復雜度跟\(k\)有關的。


正解

這題是比賽三題中思想最難,但實現最簡單的題目。
首先講講那粗暴至極的思路:
對於每一行,粗暴地建立一個多項式(請允許我這么叫),然后做FWT異或卷積。

正解是在這個基礎上進行優化。
首先,基本操作,將\(p_{i,j}\)異或上\(p_{i,0}\),把它當作不選時就是選了\(a_0\)
可以發現,在FWT過后,只有\(2^{k-1}\)種取值。(此時行的大小為\(2^m\)
接着對每一列(指的是\(n\)排多項式中的一列,就是\(2^m\)列中的一列)單獨考慮,
如果是暴力,就直接將FWT后這些位置上的數都乘起來。
但現在,我們統計對於\(2^{k-1}\)種取值,每一種取值出現的次數。最后乘起來的時候一起乘。
假如\(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\)
記這四種取值的出現次數分別為\(x_0,x_1,x_2,x_3\)
\(-\)看成\(1\),把\(+\)看成\(0\),可以通過\(a_1\)\(a_2\)的符號來計算出\(x\)的下標。
現在我們要將這四個東西都求出來。
第一個限制:\(x_0+x_1+x_2+x_3=n\),原因不解釋。

先對每個\(a\)單獨考慮。以\(a_1\)舉例子:
建立一個多項式,對於\(i\in [1,n]\),在\(p_{i,1}\)的位置加一。
對這個多項式做一遍FWT,做完之后就對於每一項,它的值就是它對應的列的\(x_0-x_1+x_2-x_3\)的值。
要解釋這個,先說說異或FWT的實質:\(FWT(F)_i=\sum_{j=0}^{L-1}(-1)^{|i\bigcap j|}F_j\)
\(|U|\)表示的是\(U\)\(1\)的個數。
推一下式子:\(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=\sum_{k=0}^{n-1}(-1)^{|i\bigcap p_{k,1}|}\)
對照一下FWT的實質,可以發現,對於第\(i\)列而言,這就是所有行對它的貢獻之和。(\(FWT(A+B)=FWT(A)+FWT(B)\)所以貢獻是可以合在一起計算的)
而這些貢獻是有正有負的。這個東西就可以理解為:正貢獻的個數減去負貢獻的個數。
再看看\(x\)\(x_0\)\(x_2\)\(a_1\)是正貢獻,\(x_1\)\(x_3\)\(a_1\)是負貢獻,跟上面所述符合。
類似地,對\(a_2\)考慮,可以求出\(x_0+x_1-x_2-x_3\)

但是現在還缺一條方程。
\(p_{k,1} \bigoplus p_{k,2}\)帶進去,\(FWT(F)_i=\sum_{k=0}^{n-1}(-1)^{|i\bigcap (p_{k,1} \bigoplus p_{k,2})|}\)
與運算滿足分配律:\(a \bigcap(b \bigoplus c)=(a\bigcap b)\bigoplus (a \bigcap c)\)
於是我們就明白了:這個東西相當於\(a_1\)\(a_2\)貢獻符號相同的個數減去貢獻符號不同的個數。在這里就是\(x_0-x_1-x_2+x_3\)
推廣一下,將正貢獻記作\(0\),負貢獻記作\(1\),那幾個東西異或起來,求出的東西是異或為\(0\)的個數減去異或為\(1\)的個數。

這樣我們就可以湊出\(2^{k-1}\)條方程,可以求解了。
但是不能直接暴力解。
觀察一下:
\(x_0+x_1+x_2+x_3\)
\(x_0-x_1+x_2-x_3\)
\(x_0+x_1-x_2-x_3\)
\(x_0-x_1-x_2+x_3\)
如果對FWT有點了解,就會發現這不就是\(FWT(\{x_0,x_1,x_2,x_3\})\)嘛!
所以做一遍IFWT,就可以解方程了。
后面的操作就不用說了吧。
時間復雜度:\(O({2^k }(n+m*2^m))\)


代碼

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#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千萬不能只會打板啊!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM