思路:
给每个点赋值(1,2,3)种的任意一个数,并保证相邻两个节点之和为奇数
由此可以推出:对每一个连通块中的任意一个奇数结点,都有两种赋值可能(1或3),统计每个连通块中的奇数(cnt1)和偶数(cnt2)顶点的个数,只考虑奇数结点的时候有
2
c
n
t
1
2^{cnt1}
2cnt1种,奇数偶数结点互换后有
2
c
n
t
2
2^{cnt2}
2cnt2,那么总的方案数就有
2
c
n
t
1
+
2
c
n
t
2
2^{cnt1}+2^{cnt2}
2cnt1+2cnt2种。
注意:
图不连通的时候,总方案数应为所有连通块的方案数累乘
当某个连通块只有一个结点的时候,该连通块方案数有3种
奇环无法染色,只要出现有奇数顶点的环,总方案数为0
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn= 3e5+10,mod=998244353;
ll t,n,m,u,v,ans,cnt1,cnt2,k,tot;
ll pre[maxn*2],head[maxn*2],vis[maxn*2];
bool flag;
struct node { //邻接表
int to,next;
} e[maxn*2];
ll qpow(ll a,ll b) { //快速幂模板
ll res = 1,base = a;
while(b) {
if(b&1) res = res*base%mod;
base = base*base%mod;
b >>= 1;
}
return res;
}
int xfind(int x) { //并查集模板
return (pre[x]==x ? x : pre[x]=xfind(pre[x]));
}
void add(int u,int v) {
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot++;
}
void dfs(int u, int f, int status) {
k++;
vis[u] = 1;
if(status == 1) cnt1++;
else cnt2++;
for (int i = head[u]; i != 0; i = e[i].next)//对邻接表的dfs
if(e[i].to != f && !vis[e[i].to])
dfs(e[i].to, u, status^1);//status用来记录当前节点u为奇数还是偶数
}
int main() {
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
tot=1,flag=true;
for(int i=0; i<=n*2; i++) pre[i]=i,vis[i]=head[i]=0;
for(int i=1; i<=m; i++) {
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
if(xfind(u)==xfind(v)) flag=false;//很巧妙,但没看太懂
pre[xfind(u)] = xfind(v+n);
pre[xfind(v)] = xfind(u+n);
}
if(!flag) cout<<"0"<<endl;
else {
ans=1;
for(int i=1; i<=n; i++) {
if(vis[i]) continue;
k=cnt1=cnt2=0;
dfs(i,0,0);
if(k==1) ans=(ans*3)%mod;
else ans *= (qpow(2,cnt1)+qpow(2,cnt2)%mod);
}
printf("%lld\n",ans%mod);
}
}
}