P4221 [WC2018] 州区划分

半在线子集卷积例题。(有点卡常)
首先判断一个点的集合是否为合法洲,这个可以先枚举点集再根据出入度和连通性判断欧拉回路,复杂度 \(O(2^nn^2)\)
\(W(s)\) 表示权值总和。
对于合法洲,其贡献函数 \(G(s)=W(s)\) ,否则为 \(G(s)=0\)。设 \(F(s)\) 表示点集 \(s\) 构成的洲区分化的所有合法方案的满意度之和。
\(F(s)=\sum\limits_{i \& j=0,i|j=S,j>0}F(i)(\frac{G(j)}{W(s)})^P\)。这是一个半在线子集卷积。初始化 \(F(0)=0\),答案为 \(F(\)满集\()\)
考虑普通的子卷积,把 \(1\) 的个数 \(x\) 提到前面,按 \(x\) 从小到大做。

code
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=(1<<21)+5,Mod=998244353;
int n,m,P,S,Eu[225],Ev[225],ind[23],w[23],cz[N],fa[23],vf[23];
LL W[N],g[N],F[22][N],G[22][N];
int find(int x){
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
LL Pow(LL x,LL k){
	LL y=1;
	while(k){
		if(k & 1) y=y*x%Mod;
		k>>=1; x=x*x%Mod;
	}
	return y;
}
void FWT(LL *f,int n,int ff){
	for(int p=2;p<=n;p<<=1){
		int pn=p>>1;
		for(int i=0;i<n;i+=p)
			for(int j=i;j<i+pn;j++)
				f[j+pn]=(f[j+pn]+f[j]*ff+Mod)%Mod;		
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&P); S=1<<n;
	for(int i=1;i<S;i++) cz[i]=cz[i>>1]+(i & 1);
	for(int i=1;i<=m;i++) {
		scanf("%d%d",&Eu[i],&Ev[i]);
		Eu[i]--; Ev[i]--;
	}
	for(int i=0;i<n;i++) scanf("%d",&w[i]);
	for(int i=1;i<S;i++){
		for(int j=0;j<n;j++) {
			ind[j]=0; fa[j]=j; vf[j]=0;
			if((i>>j) & 1) W[i]+=w[j];
		}
		W[i]=Pow(W[i],P);
		for(int j=1;j<=m;j++)
			if(((i>>Eu[j]) & 1) && ((i>>Ev[j]) & 1)) {
				ind[Eu[j]]++,ind[Ev[j]]++;
				fa[find(Eu[j])]=find(Ev[j]);
			}
		int c=0,r=0;
		for(int j=0;j<n;j++)
			if(((i>>j) & 1) && !vf[find(j)]) vf[fa[j]]=1,c++;
		for(int j=0;j<n;j++) if(ind[j]%2) {r=1; break;}
		if(r || c>1) g[i]=W[i];
		W[i]=Pow(W[i],Mod-2);
	}
	for(int i=0;i<S;i++) G[cz[i]][i]=g[i];
	for(int i=0;i<=n;i++) FWT(G[i],S,1);
	F[0][0]=1; FWT(F[0],S,1);
	for(int i=1;i<=n;i++){
		for(int j=0;j<i;j++)
			for(int k=0;k<S;k++) (F[i][k]+=F[j][k]*G[i-j][k])%=Mod;
		FWT(F[i],S,-1);
		for(int j=0;j<S;j++)
			if(cz[j]==i) (F[i][j]*=W[j])%=Mod;
			else F[i][j]=0;
		if(i!=n) FWT(F[i],S,1);
	}
	printf("%lld",F[n][S-1]);
	return 0;
}
posted @ 2023-08-09 21:07  Shui_dream  阅读(35)  评论(0)    收藏  举报