ABC294Ex K-Coloring

Statement

对一张简单无向图进行 \(k\) 染色,满足对于每条边的两个端点颜色不同,求方案数。

\(n,m\leq 30\)

Solution

无向图 \(k\) 染色问题,很经典的问题。

这道题的突破口是 \(n,m\) 均不大,所以 \(m-n\) 不会很大,这提示我们使用广义串并联图方法

具体地,根据 EI 和洛谷讨论区里的说法,我们套路性地考虑对于每条边设 \(DP\)\(f_i\) 表示如果这条边两个端点被染了不同的颜色,这条边内部被缩略的结构中有多少种染色方案。\(g_i\) 表示如果这两条边端点被染了相同颜色的方案数。

那么对于原图中的边显然有 \(f_{u,v}=1\)\(g_{u,v}=0\)

广义串并联图方法的套路是对每条边设置 \(DP\) 后删一度点直接把贡献乘入答案,缩二度点和叠重边更新 \(DP\) 值。

下述推导来自讨论区:

对于删一度点,将答案乘上 \((k-1)f_u+g_u\),表示枚举删的这个点的颜色。

对于缩二度点,\(f_e=f_u f_v(k-2)+g_u f_v+f_u g_v,g_e=f_u f_v(k-1)+g_u g_v\),表示枚举中间那个点的颜色并分讨。

对于叠重边,\(f_e=f_u f_v,g_e=g_u g_v\),表示乘法原理。

现在图中的点满足了 \(n\leq \frac{2m}{3}\)\(n\leq 20\)

考虑对每一个颜色设一个集合幂级数,答案就是这些集合幂级数子集卷积的结果。

具体地,我们先假设所有边都取到了 \(f\) 的贡献,然后如果有一个颜色的集合包含了这条边的两个端点,就需要乘上一个 \(\frac{g}{f}\)。容易发现 \(f\) 总是非 \(0\) 的,所以一定存在逆元。这些贡献容易一遍 \(\text{FWT}\) 计算答案。

现在我们需要快速求集合幂级数 \(F\)\(k\) 次方。然而 \(n\)\(20\) 级别,所以需要 \(\ln\)\(\exp\) 回去。复杂度是 \(O(2^n n^2)\) 的。

\(\ln,\exp\) 直接对占位幂级数 \(O(n^2)\) 求就可以了。

式子:
\(\ln:g_n=f_n-\frac{1}{n}\sum_{i=1}^{n-1} g_i i f_{n-i}\)。需要保证常数项为 \(1\)

\(\exp:g_n=\frac{1}{n}\sum_{i=1}^{n} f_i i g_{n-i}\)。需要保证常数项为 \(0\)

#include <cstdio>
using namespace std;
int read(){
	char c=getchar();int x=0;
	while(c<48||c>57) c=getchar();
	do x=(x<<1)+(x<<3)+(c^48),c=getchar();
	while(c>=48&&c<=57);
	return x;
}
const int N=33,P=998244353;
typedef long long ll;
int qp(int a,int b=P-2){
	int res=1;
	while(b){
		if(b&1) res=(ll)res*a%P;
		a=(ll)a*a%P;b>>=1;
	}
	return res;
}
int n,m,k,res,cnt;
int f[N][N],g[N][N];
bool del[N];
int deg[N];
int F[1<<20];
int inv[21],id[N];
namespace Subset{
	int n;
	int f[21][1<<20];
	int g[21][1<<20];
	void inc(int &x,int v){if((x+=v)>=P) x-=P;}
	void dec(int &x,int v){if((x-=v)<0) x+=P;}
	void FWT(int *arr){
		for(int i=1;i<(1<<n);i<<=1)
			for(int j=0;j<(1<<n);j+=(i<<1))
				for(int k=j;k<(j|i);++k) inc(arr[k|i],arr[k]);
	}
	void IFWT(int *arr){
		for(int i=1;i<(1<<n);i<<=1)
			for(int j=0;j<(1<<n);j+=(i<<1))
				for(int k=j;k<(j|i);++k) dec(arr[k|i],arr[k]);
	}
	void getln(int *arr){
		for(int i=0;i<=n;++i)
			for(int s=0;s<(1<<n);++s) f[i][s]=g[i][s]=0;
		for(int s=0;s<(1<<n);++s) f[__builtin_popcount(s)][s]=arr[s];
		for(int i=0;i<=n;++i) FWT(f[i]);
		for(int i=1;i<=n;++i){
			for(int j=1;j<i;++j)
				for(int s=0;s<(1<<n);++s)
					dec(g[i][s],(ll)g[j][s]*j%P*f[i-j][s]%P);
			for(int s=0;s<(1<<n);++s)
				g[i][s]=((ll)g[i][s]*inv[i]+f[i][s])%P;
		}
		for(int i=0;i<=n;++i) IFWT(g[i]);
		for(int s=0;s<(1<<n);++s) arr[s]=g[__builtin_popcount(s)][s];
	}
	void getexp(int *arr){
		for(int i=0;i<=n;++i)
			for(int s=0;s<(1<<n);++s) f[i][s]=g[i][s]=0;
		for(int s=0;s<(1<<n);++s) f[__builtin_popcount(s)][s]=arr[s];
		for(int i=0;i<=n;++i) FWT(f[i]);
		for(int s=0;s<(1<<n);++s) g[0][s]=1;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=i;++j)
				for(int s=0;s<(1<<n);++s)
					inc(g[i][s],(ll)f[j][s]*j%P*g[i-j][s]%P);
			for(int s=0;s<(1<<n);++s)
				g[i][s]=(ll)g[i][s]*inv[i]%P;
		}
		for(int i=0;i<=n;++i) IFWT(g[i]);
		for(int s=0;s<(1<<n);++s) arr[s]=g[__builtin_popcount(s)][s];
	}
}
int main(){
	n=read();m=read();k=read();res=1;
	for(int i=1;i<=m;++i){
		int u=read(),v=read();
		f[u][v]=f[v][u]=1;
		++deg[u];++deg[v];
	}
	bool fl=1;
	while(fl){
		fl=0;
		for(int u=1;u<=n;++u)
			if(deg[u]==1){
				fl=1;
				del[u]=1;
				for(int v=1;v<=n;++v)
					if(f[u][v]){
						--deg[u];--deg[v];
						res=((ll)f[u][v]*(k-1)+g[u][v])%P*res%P;
						f[u][v]=f[v][u]=0;
						g[u][v]=g[v][u]=0;
					}
				break;
			}
		if(fl) continue;
		for(int u=1;u<=n;++u)
			if(deg[u]==2){
				fl=1;
				int x=0,y=0;
				for(int v=1;v<=n;++v)
					if(f[u][v]){if(x) y=v;else x=v;}
				deg[u]=0;del[u]=1;
				int ff=(ll)f[u][x]*f[u][y]%P;
				int nf=((ll)ff*(k-2)+(ll)g[u][x]*f[u][y]+(ll)f[u][x]*g[u][y])%P;
				int ng=((ll)ff*(k-1)+(ll)g[u][x]*g[u][y])%P;
				f[u][x]=f[x][u]=f[u][y]=f[y][u]=0;
				g[u][x]=g[x][u]=g[u][y]=g[y][u]=0;
				if(!f[x][y]&&!f[y][x]){
					f[x][y]=f[y][x]=nf;
					g[x][y]=g[y][x]=ng;
				}
				else{
					f[y][x]=f[x][y]=(ll)f[x][y]*nf%P;
					g[y][x]=g[x][y]=(ll)g[x][y]*ng%P;
					--deg[x];--deg[y];
				}
				break;
			}
	}
	for(int i=1;i<=n;++i) if(!del[i]&&!deg[i]) res=(ll)res*k%P,del[i]=1;
	for(int i=1;i<=n;++i)
		if(!del[i]) id[i]=cnt++;
	if(cnt){
		inv[1]=1;Subset::n=cnt;
		for(int i=2;i<=cnt;++i) inv[i]=(ll)inv[P%i]*(P-P/i)%P;
		for(int i=0;i<(1<<cnt);++i) F[i]=1;
		for(int i=1;i<=n;++i){
			if(del[i]) continue;
			for(int j=1;j<i;++j){
				if(del[j]) continue;
				if(f[i][j]){
					res=(ll)res*f[i][j]%P;
					int ver=(1<<id[i])|(1<<id[j]);
					F[ver]=(ll)F[ver]*qp(f[i][j])%P*g[i][j]%P;
				}
			}
		}
		for(int i=1;i<(1<<cnt);i<<=1)
			for(int j=0;j<(1<<cnt);j+=(i<<1))
				for(int k=j;k<(j|i);++k) F[k|i]=(ll)F[k|i]*F[k]%P;
		Subset::getln(F);
		for(int i=0;i<(1<<cnt);++i) F[i]=(ll)F[i]*k%P;
		Subset::getexp(F);
		res=(ll)res*F[(1<<cnt)-1]%P;
	}
	printf("%d\n",res);
	return 0;
}
posted @ 2023-03-21 07:48  yyyyxh  阅读(92)  评论(0编辑  收藏  举报