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千万不能只会打板啊!