1. 程式人生 > 實用技巧 >uoj310【UNR #2】黎明前的巧克力(FWT)

uoj310【UNR #2】黎明前的巧克力(FWT)

uoj310【UNR #2】黎明前的巧克力(FWT)

uoj

題解時間

對非零項極少的FWT的優化。

首先有個十分好想的DP: $ f[i][j] $ 表示考慮了前 $ i $ 個且異或和為 $ j $ 的方案數,

有 $ f[i][j]= f[i-1][j] + 2 * f[i-1][j \oplus a[i]] $ 。

可以考慮FWT,但很明顯時間複雜度沒有優化。

但另一方面,每層的卷積卷的都是 $ 1,0,0,...,2,0,0,... $ 的形式,

這樣一來卷之後每項都是 $ -1 $ 或 $ 3 $ 。

因此大膽一點直接把所有的式子加到一起卷積,

卷完有什麼用呢?

這樣卷出來的結果是每層卷積相加,而正常FWT求解是每層卷積相乘。

每一位都是共計 $ n $ 個 $ -1 $ 和 $ 3 $ 加一起,所以可以求出每一位上兩種數的個數。

由於知道了每一位上兩種數的個數,直接每一位快速冪就能求出原來的相乘結果。

#include<bits/stdc++.h>
using namespace std;
typedef long long lint;
struct pat{int x,y;pat(int x=0,int y=0):x(x),y(y){}bool operator<(const pat &p)const{return x==p.x?y<p.y:x<p.x;}};
template<typename TP>inline void read(TP &tar)
{
	TP ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){ret=ret*10+(ch-'0');ch=getchar();}
	tar=ret*f;
}
template<typename TP,typename... Args>inline void read(TP& t,Args&... args){read(t),read(args...);}
namespace RKK
{
const int N=2000011;
const int defaultlen=1048576;
const int mo=998244353,inv2=499122177,inv4=748683265;
void doadd(int &a,int b){if((a+=b)>=mo) a-=mo;}int add(int a,int b){return (a+=b)>=mo?a-mo:a;}
void domul(int &a,int b){a=1ll*a*b%mo;}int mul(int a,int b){return 1ll*a*b%mo;}
int fpow(int a,int p){int ret=1;while(p){if(p&1) domul(ret,a);domul(a,a),p>>=1;}return ret;}
int n,a[N];
void fwtxor(int *a,int len,int tp)
{
	for(int i=1,w1,w2;i<len;i<<=1)
	for(int j=0;j<len;j+=i<<1)
	for(int k=0;k<i;k++)
	{
		w1=a[j+k],w2=a[j+k+i];
		a[j+k]=add(w1,w2),a[j+k+i]=add(w1,mo-w2);
		if(tp==-1) domul(a[j+k],inv2),domul(a[j+k+i],inv2);
	}
}
int main()
{
	read(n);for(int i=1,w;i<=n;i++) read(w),a[0]+=1,a[w]+=2;
	fwtxor(a,defaultlen,1);
	for(int i=0,cnt1,cnt3;i<defaultlen;i++)
	{
		cnt3=mul(add(n,a[i]),inv4),cnt1=n-cnt3;
		a[i]=(cnt1&1)?mo-fpow(3,cnt3):fpow(3,cnt3);
	}
	fwtxor(a,defaultlen,-1);
	printf("%d\n",add(a[0],mo-1));
	return 0;
}
}
int main(){return RKK::main();}