QOJ856 Cactus 广义串并联图

题意

给定一棵仙人掌, 你需要用 \(k\) 种颜色给每个结点染色, 且保证有边相连的结点的颜色不相同, 求染色的方案数对 \(10^9+7\) 取模的结果。仙人掌定义为一张特殊的无向图, 其中每条边至多在一个简单环上。

题解

因为是仙人掌,考虑广义串并联图方法。
对每条广义串并联图上的边,记 \(f,g\) 分别表示已确定两端点颜色,两端点颜色相同两端点颜色不同的方案数。考虑如何压缩:

  • 叠合重边:\(f,g\) 分别相乘即可。
  • \(1\) 度点:将答案乘上 \(f+(k-1)g\) 即可。
  • \(2\) 度点:记两条边分别为 \(u,v\),则合并以后边的 \(f=f_uf_v+g_ug_v(k-1),\ g=f_ug_v+g_uf_v+g_ug_v(k-2)\)

最后再将答案乘上 \(k\) 就做完了。

参考代码:

#include<bits/stdc++.h>
#define ll long long
#define mxn 300003
#define md 1000000007
#define pb push_back
#define mkp make_pair
#define ld long double
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define rept(i,a,b) for(int i=(a);i<(b);++i)
#define drep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
int T,n,m,k;
map<int,pair<ll,ll>>mp[mxn];
queue<int>q;
ll ans;
inline void add(int x,int y,ll f,ll g){
	if(mp[x].find(y)!=mp[x].end()){
		auto d=mp[x][y];
		mp[x][y]=mp[y][x]={f*d.first%md,g*d.second%md};
	}else mp[x][y]=mp[y][x]={f,g};
}
void solve(){
	scanf("%d%d%d",&n,&m,&k);
	rep(i,1,n)mp[i].clear();
	ans=1;
	for(int i=0,x,y;i<m;++i){
		scanf("%d%d",&x,&y);
		add(x,y,0,1);
	}
	rep(i,1,n)if(mp[i].size()<3)q.push(i);
	while(q.size()){
		int x=q.front();q.pop();
		if(mp[x].size()==1){
			ans=(mp[x].begin()->second.first+mp[x].begin()->second.second*(k-1))%md*ans%md;
		}else if(mp[x].size()==2){
			auto it=mp[x].begin();
			int y1=it->first;auto z1=it->second;it++;
			int y2=it->first;auto z2=it->second;
			add(y1,y2,(z1.first*z2.first+z1.second*z2.second%md*(k-1))%md,(z1.first*z2.second+z1.second*z2.first+z1.second*z2.second%md*(k-2))%md);
		}
		for(auto i:mp[x]){
			mp[i.first].erase(x);
			if(mp[i.first].size()<3)q.push(i.first);
		}
		mp[x].clear();
	}
	cout<<ans*k%md<<'\n';
}
signed main(){
	scanf("%d",&T);
	while(T--)solve();
	return 0;
}
posted @ 2025-10-08 21:49  zifanwang  阅读(16)  评论(0)    收藏  举报