loj2340 WC2018 州区划分 状压dp+FWT

Description


题面到处都有系列。。

Solution


FMT是啥,能吃吗
首先考虑怎么判合法子图(也就是欧拉回路),我们n2*2n枚举点然后统计度数就可以了
那么一个比较显然的dp就是设f[S]表示二进制状态为S的所有答案,g[S]表示S这个集合分成一份的贡献
我们枚举S的子集转移即可,这样做是O(3n)的

考虑把柿子写出来,那么就是 f S = ∑ T ⊂ S f T ⋅ g S ∖ T f_{S}=\sum\limits_{T\subset S}f_{T}\cdot g_{S\setminus T} fS=TSfTgST
这实际上就是一个子集卷积,只不过这个有自己卷自己的部分。
注意到 ∑ T ⊂ S f T ⋅ g S ∖ T = ∑ U ⊂ S , V ⊂ S , U ∪ V = S , U ∩ V = ∅ f U ⋅ g V = ∑ U ⊂ S , V ⊂ S , ∣ U ∣ + ∣ V ∣ = ∣ S ∣ , U ∪ V = S f U ⋅ g V \sum\limits_{T\subset S}{f_{T}\cdot g_{S\setminus T}}=\sum\limits_{U\subset S,V\subset S,U\cup V=S,U\cap V=\emptyset}{f_{U}\cdot g_{V}}=\sum\limits_{U\subset S,V\subset S,|U|+|V|=|S|,U\cup V=S}f_{U}\cdot g_{V} TSfTgST=US,VS,UV=S,UV=fUgV=US,VS,U+V=S,UV=SfUgV
这里的转化十分的巧妙
于是我们设 f i , S f_{i,S} fi,S表示二进制中i位是1,状态为S的答案,我们对i做卷积,然后对S做或卷积就可以了
于是复杂度就是 O ( n 2 ⋅ 2 n ) O(n^2\cdot2^n) O(n22n)的了,我写的代码要卡一卡常数。。

Code


#include 
#include 
#include 
#define rep(i,st,ed) for (register int i=st;i<=ed;++i)

typedef long long LL;
const int MOD=998244353;
const int N=2197152;

LL f[22][N],g[22][N],s[N],inv[N];

int rc[22][22],fa[22],d[22],w[22],c[N];
int n,m,p,lim;

void upd(LL &x,LL y) {
	x+=y; (x>=MOD)?(x-=MOD):0;
}

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void FWT(LL *a,int f) {
	for (register int i=1;i<lim;i<<=1) {
		for (register int j=0;j<lim;j+=(i<<1)) {
			for (register int k=0;k<i;++k) {
				if (f==1) upd(a[j+k+i],a[j+k]);
				else a[j+k+i]=(a[j+k+i]+MOD-a[j+k])%MOD;
			}
		}
	}
}

LL ksm(LL x,LL dep) {
	LL res=1;
	for (;dep;dep>>=1) {
		(dep&1)?(res=res*x%MOD):0;
		x=x*x%MOD;
	}
	return res;
}

int find(int x) {
	return !fa[x]?x:(fa[x]=find(fa[x]));
}

bool merge(int x,int y) {
	x=find(x),y=find(y);
	if (x==y) return false;
	return 1|(fa[x]=y);
}

bool check(int S) {
	rep(i,1,n) fa[i]=d[i]=0;
	int cnt=1;
	rep(i,1,n) if (S>>(i-1)&1) {
		s[S]=(s[S]+w[i])%MOD;
		rep(j,i+1,n) if (S>>(j-1)&1) {
			if (!rc[i][j]) continue;
			++d[i],++d[j];
			cnt+=merge(i,j);
		}
	}
	inv[S]=ksm(ksm(s[S],MOD-2),p);
	rep(i,1,n) if (S>>(i-1)&1) {
		if (d[i]&1) return false;
		cnt--;
	}
	return !cnt;
}

int main(void) {
	freopen("data.in","r",stdin);
    freopen("myp.out","w",stdout);
	n=read(),m=read(),p=read();
	lim=(1<<n)-1;
	rep(i,1,m) {
		int x=read(),y=read();
		rc[x][y]=rc[y][x]=1;
	}
	rep(i,1,n) w[i]=read();
	rep(i,0,lim) c[i]=c[i>>1]+(i&1);
	rep(i,1,lim) if (!check(i)) {
		// printf("%d\n", i);
		g[c[i]][i]=ksm(s[i],p);
	}
	f[0][0]=1;
	rep(i,0,n-1) f[1][1<<i]=(g[1][1<<i]!=0);
	rep(i,1,n) FWT(g[i],1);
	FWT(f[0],1);
	FWT(f[1],1);
	rep(i,2,n) {
		rep(j,1,i) rep(k,0,lim) {
			upd(f[i][k],g[j][k]*f[i-j][k]%MOD);
		}
		FWT(f[i],-1);
		rep(k,0,lim) {
			if (c[k]!=i) f[i][k]=0;
			else f[i][k]=f[i][k]*inv[k]%MOD;
		}
		FWT(f[i],1);
		// for (int j=0;j<=lim;++j) printf("%lld ", f[i][j]); puts("");
	}
	FWT(f[n],-1);
	printf("%lld\n", f[n][lim]);
	return 0;
}

你可能感兴趣的:(c++,FWT,状压dp)