[ABC294Ex] K-Coloring

考虑 dfs 后搞出 dfs 树,考虑若干返祖边有限制,那么,我们一个朴素的想法是枚举这些有被返祖边搞到的点的颜色,但这样最坏是 \(O(K^n)\) 的。

但显然一条返祖边在钦定完一个端点的颜色之后,另一端的颜色便可以不用钦定,因此指数可以减小一点。

那么我们可以钦定每个返祖边深度小的点的颜色,但是直接钦定是 \(O(K^t)\) 的,\(t\) 是返祖边深度小的点的集合大小。考虑我们并不关心其真正的颜色是什么,因此这东西可以集合划分后再给每个集合分配颜色。

那么这样子,你就有若干点的颜色钦定了,记钦定的颜色数量为 \(c\),但整棵树的染色显然不可能只用 \(c\) 种,但其他的颜色我们都是未钦定的,考虑另分一类给这些颜色。

\(f(x,i)\) 表示 \(x\) 为第 \(i\) 种颜色的时候,其子树内染色的方案数。

考虑转移,对于树边的转移是朴素的。

对于返祖边的话,由于我们已经钦定了深度小的点的颜色,那么当前点的颜色也必须与其相同,不相同的方案数直接没了即可。

注意之后方案数应乘上对该集合划分分配颜色的方案数。

不重不漏的证明:考虑一种合法的染色方案,显然会被在钦定其返祖点的颜色时钦定到这种方案,且只钦定到一次。假如一种方案被钦定了两次的话。若颜色划分的集合不同,那么两种染色方案一定不同。若颜色划分的集合相同,但对集合分配的颜色方案不同,则两种染色方案一定不同。则关键,在于转移。而我们的转移并不会重算同一种方案,因为其都是子树各自考虑而又独立合并的。所以一种方案不会被钦定多次。

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int mod=998244353;
struct edge {
	int nex,to;
}e[70];
int f[35][35],n,m,K,dfn[35],tot,otot,o[35],rt,col[35],fa[35],hea[35],cnt=1;

void add_edge(int x,int y) {
	e[++cnt].nex=hea[x]; e[cnt].to=y; hea[x]=cnt;
}

void dfs0(int x,int pre) {
	dfn[x]=++tot;
	for(int i=hea[x];i;i=e[i].nex) {
		if((i==(pre^1))) continue ;
		int y=e[i].to;
		if(!dfn[y]) dfs0(y,i);
		else if(dfn[y]<=dfn[x]) {
			o[++otot]=y;
		}
	}
}

bool vis[35];
int C;
void dp(int x,int pre) {
	if(!col[x]) {
		for(int i=0;i<=C;i++) f[x][i]=1;
	} else {
		for(int i=0;i<=C;i++) f[x][i]=0;
		f[x][col[x]]=1;
	}
	vis[x]=1;
//	cout<<x<<" "<<ff<<" in\n";
	for(int j=hea[x];j;j=e[j].nex) {
		if((j==(pre^1))) continue ;
		int y=e[j].to;
		if(!vis[y]) {
			dp(y,j);
			int qwq=f[y][0]*(K-C-1)%mod;
			for(int i=1;i<=C;i++) qwq=(qwq+f[y][i])%mod;
			f[x][0]=f[x][0]*qwq%mod;
			qwq=0;
			for(int i=1;i<=C;i++) qwq=(qwq+f[y][i])%mod;
			for(int i=1;i<=C;i++) {
				int qaq=(f[y][0]*(K-C)%mod+(qwq-f[y][i]+mod)%mod)%mod;
				f[x][i]=f[x][i]*qaq%mod;
			}
		} else if(dfn[y]<=dfn[x]) {
			for(int i=0;i<=C;i++) {
				if(i==col[y]) f[x][i]=0;
			}
		}
	}
//	cout<<": "<<x<<" -> "<<col[x]<<'\n';
//	for(int i=0;i<=C;i++) cout<<f[x][i]<<' ';
//	cout<<'\n';
}

int nwans,ans;
void dfs(int x,int c) {
	if(c>K) return ;
	if(x==otot+1) {
		for(int i=1;i<=n;i++) vis[i]=0;
		C=c; dp(rt,0);
		int res=f[rt][0]*(K-C)%mod;
		for(int i=1;i<=C;i++) res=(res+f[rt][i])%mod;
		for(int i=K-C+1;i<=K;i++) res=res*i%mod;
		nwans=(nwans+res)%mod;
		return ;
	}
	for(int i=1;i<=c;i++) col[o[x]]=i,dfs(x+1,c);
	col[o[x]]=c+1; dfs(x+1,c+1);	
}

signed main() {
	cin.tie(0); ios::sync_with_stdio(false);
	cin>>n>>m>>K;
	for(int i=1;i<=m;i++) {
		int x,y; cin>>x>>y; add_edge(x,y); add_edge(y,x);
	}
	ans=1;
	for(int i=1;i<=n;i++) {
		if(!dfn[i]) {
			rt=i; nwans=0; otot=0;
			dfs0(rt,0);
			sort(o+1,o+1+otot);
			if(otot) otot=unique(o+1,o+1+otot)-o-1;
//			for(int j=1;j<=otot;j++) cout<<o[j]<<' ';
//			cout<<'\n';
			dfs(1,0);
//			cout<<nwans<<'\n';
			ans=ans*nwans%mod;
		}
	}
	ans=(ans%mod+mod)%mod; cout<<ans;
	return 0;
}
posted @ 2023-03-20 16:42  FxorG  阅读(30)  评论(0编辑  收藏  举报