题目
题意概要
求简单无向连通图的
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
1⩽m⩽n+5⩽105 。提示:本问题为
N
P
C
NPC
NPC 问题。
思路
挺套路的。将一度点扔掉,将二度链转化为:如果顶点颜色相同,贡献为 v 0 v_0 v0,否则为 v 1 v_1 v1 。那么一共有 2 ( m − n ) = 10 2(m-n)=10 2(m−n)=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 310≈5×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;
}