今天我也要当炼铜

122 篇文章 0 订阅
该博客探讨了一种NPC问题——求解简单无向连通图的k染色数量。文章介绍了染色问题的背景,数据范围和提示,并提出了基于图论的解决方案,包括将一度点移除、处理二度链,以及使用动态规划和状态压缩的方法。博主还提供了C++代码实现,展示了如何通过暴力搜索和预处理优化计算复杂度。
摘要由CSDN通过智能技术生成

题目

题意概要
求简单无向连通图的 k k k 染色数量。 k k k 染色指:将每个顶点染上 k k k 种颜色的一种,使得任意一条边都连接两个颜色不同的顶点。

数据范围与提示
1 ⩽ m ⩽ n + 5 ⩽ 1 0 5 1\leqslant m\leqslant n+5\leqslant 10^5 1mn+5105 。提示:本问题为 N P C NPC NPC 问题。

思路

套路的。将一度点扔掉,将二度链转化为:如果顶点颜色相同,贡献为 v 0 v_0 v0,否则为 v 1 v_1 v1 。那么一共有 2 ( m − n ) = 10 2(m-n)=10 2(mn)=10 个特殊点,最多有 10 × 10 × 2 10\times 10\times 2 10×10×2 条所谓的边。

此时完全可以暴搜 10 10 10 个点的染色方案,因为 10 10 10 的整数拆分只有 1 0 4 10^4 104 多一点。即使是 O ( 10 × 10 × 2 ) \mathcal O(10\times 10\times 2) O(10×10×2) 的计算贡献都可以。

当然更好的方法是 2 10 × 10 2^{10}\times 10 210×10 预处理,然后就可以在暴搜的过程中 O ( 1 ) \mathcal O(1) O(1) 顺带计算贡献了。有人要写这个吗

甚至还可以由暴搜改为 3 10 3^{10} 310 的状压,每次找一个子集染成同色。上面的 2 10 × 10 2^{10}\times 10 210×10 的预处理数组拿来用,再搞出一个 3 10 3^{10} 310 的权值表,就是完全 3 10 3^{10} 310 了……

不过 3 10 ≈ 5 × 1 0 3 3^{10}\approx 5\times 10^3 3105×103,跟 10 10 10 的整数分拆也没差多少……

代码

体验一次炼铜暴力随便过题的快感

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 100015;
struct Edge{
	int to, nxt;
	Edge(int T=0,int N=0){
		to = T, nxt = N;
	}
};
Edge e[MaxN<<1];
int head[MaxN], cntEdge;
void addEdge(int a,int b){
	e[cntEdge] = Edge(b,head[a]);
	head[a] = cntEdge ++;
}

const int Mod = 1e9+7;
inline int qkpow(int_ b,int q){
	if(q == 1) return b;
	int_ a = 1;
	for(; q; q>>=1,b=b*b%Mod)
		if(q&1) a = a*b%Mod;
	return a;
}

int wxk, deg[MaxN], k;
void topo(int x){
	deg[x] = 0; wxk = wxk*int_(k-1)%Mod;
	for(int i=head[x]; ~i; i=e[i].nxt){
		-- deg[e[i].to];
		if(!deg[e[i].to]) wxk = wxk*int_(k)%Mod;
		if(deg[e[i].to] == 1) topo(e[i].to);
	}
}

int len; // length of chain
int findChain(int x,int pre){
	++ len; // one more edge
	if(deg[x] > 2) return x;
	for(int i=head[x]; ~i; i=e[i].nxt){
		if(e[i].to == pre) continue;
		if(deg[e[i].to] > 0)
			return findChain(e[i].to,x);
	}
	return -1; // impossible
}

const int MaxK = 20;
int g[MaxK][MaxK][2]; // contribution between two special nodes
int dp[MaxN][2]; // i-th node's choice
int tot; // count of special nodes
int bel[MaxK]; // choose a color
int ans; // accumulate
void calc(int v){
	rep(i,1,tot) rep(j,1,i){
		int opt = (bel[i] != bel[j]);
		v = int_(v)*g[i][j][opt]%Mod;
	}
	ans = (ans+v)%Mod;
}
void dfs(int x,int cnt,int v){
	if(x == tot+1) return calc(v);
	for(bel[x]=1; bel[x]<=cnt; ++bel[x])
		dfs(x+1,cnt,v); // same as others
	if(cnt == k) return ; // cannot add
	v = int_(v)*(k-cnt)%Mod;
	bel[x] = cnt+1; dfs(x+1,cnt+1,v);
}

vector<int> tmp[MaxK];
int haxi[MaxN];
int main(){
	int n = readint(), m = readint();
	memset(head+1,-1,n<<2);
	wxk = 1, k = readint();
	for(int i=1,x,y; i<=m; ++i){
		x = readint(), y = readint();
		addEdge(x,y), addEdge(y,x);
		++ deg[x], ++ deg[y];
	}
	rep(i,1,n) if(deg[i] == 1) topo(i);
	dp[1][0] = 0, dp[1][1] = 1;
	rep(i,2,n){
		dp[i][0] = int_(k-1)*dp[i-1][1]%Mod;
		dp[i][1] = (int_(k-2)*dp[i-1][1]+dp[i-1][0])%Mod;
	}
	rep(i,1,n) if(deg[i] > 2){
		haxi[i] = ++ tot;
		rep(j,1,tot){
			g[tot][j][0] = g[tot][j][1] = 1;
			tmp[j].clear(); // good habit
		}
		for(int j=head[i]; ~j; j=e[j].nxt)
			if(deg[e[j].to] > 0){
				len = 0; // count
				int to = findChain(e[j].to,i);
				if(to <= i) // hashed
					tmp[haxi[to]].push_back(len);
			}
		rep(j,1,tot){
			sort(tmp[j].begin(),tmp[j].end());
			int siz = tmp[j].size();
			for(int l=0,r; l!=siz; l=r){
				for(r=l; r!=siz&&tmp[j][r]==tmp[j][l]; ++r);
				rep(pp,0,1) g[tot][j][pp] = int_(g[tot][j][pp])*
					qkpow(dp[tmp[j][l]][pp],(r-l)>>(j==tot))%Mod;
			}
		}
	}
	if(tot == 0){ // all deg = 2, big cycle
		rep(i,(len=0)+1,n) len += (deg[i] == 2);
		ans = dp[len-1][1]*int_(k-1)%Mod*k%Mod;
	}
	else dfs(1,0,1);
	ans = int_(ans)*wxk%Mod;
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值