题意
n个点m条边的无向图,每条边有一个权值,-1,0,1,表示这条边的染色情况,-1表示没有染色,0,1表示染的颜色。
需要对没有染色的边进行染色(0和1两种),求满足所有环的边异或和为0的染色方案数。
题解
- 直接对边赋值很难搞,那么考虑对点赋值,再转换为边的赋值,边的值就是两个端点的异或值,这样的好处就在于,在环中的一个点的权值会被相邻边的异或两次,两个相同的数异或就为0,这样就可以保证环的边异或和为0.
- 考虑这样赋值的方案数,每个点可以有两种选择,在n个点连通的情况下,n个点就有2^n种方案,那么边的方案是多少呢,两个相邻点(x,y),[x权值为1,y权值为0]和[x权值为0,y权值为1]的情况下,边的权值是相同的,类似的,[x权值为1,y权值为1]和[x权值为0,y权值为0]也是相同的,由此可以得出,边的染色方案是2^n-1,也就是点染色方案数的1/2.
- 接下来考虑有的边已经赋值的情况,对于赋值了的边(x,y),我们对x点赋值,就能得出y的点值(x^y=(x,y)那么x^(x,y)=y)。也就是说,对于一些赋值了的边的连通块,只要确定了一个点的权值,其他所有的点就能确定了,假设这些通过赋值了的边连在一起的点的个数是k,那么总的方案数就要除以2^(k-1).
- 计算方案数之前要判断原图是否已经是不满足条件的了.
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 7, mod = 998244353; typedef long long ll; int head[maxn], to[maxn<<1], nex[maxn<<1], edge[maxn<<1], tot; void init() { memset(head, -1, sizeof(head)); tot = 0; } void add(int x, int y, int z) { to[tot] = y; edge[tot] = z; nex[tot] = head[x]; head[x] = tot++; } int col[maxn], vis[maxn], sum; int dfs(int x, int c) { col[x] = c; for (int i = head[x]; ~i; i = nex[i]) { int v = to[i]; if(edge[i] == -1) continue;//改边如果是没有限定权值的,直接跳过,这条边不会对点染色产生影响 if(col[v] != -1 && (c^edge[i] != col[v])) return 0;//如果v点已经染色,那么通过边权值异或点权值来判断v点是否与染色矛盾 if(col[v] == -1 && (dfs(v, c^edge[i]) == 0)) return 0;//如果v没有染色,dfs继续判断是否矛盾,通过边点权值异或传递权值 } return 1; } void dfs1(int x, int fx) { if(vis[x]) return; vis[x] = 1; sum++; for (int i = head[x]; ~i; i = nex[i]) { int v = to[i]; if(v == fx) continue; dfs1(v, x); } } void dfs2(int x, int fx) { if(vis[x]) return; vis[x] = 1; sum++; for (int i = head[x]; ~i; i = nex[i]) { int v = to[i]; if(edge[i] == -1) continue; if(v == fx) continue; dfs2(v, x); } } int main() { int n, m; init(); scanf("%d%d", &n, &m); for (int i = 1, u, v, w; i <= m; i++) { scanf("%d%d%d", &u, &v, &w); add(u, v, w); add(v, u, w); } /*判断原图是否已经不满足环的异或和为0了*/ memset(col, -1, sizeof(col)); for (int i = 1; i <= n; i++) { if(col[i] == -1) {//如果这个点没有染色 if(dfs(i, 1) == 0) {//给这个点染成1, dfs判断是否矛盾 printf("0\n"); return 0; } } } /*计算连通块的边数和*/ memset(vis, 0, sizeof(vis)); int k = 0; for (int i = 1; i <= n; i++) { if(!vis[i]) { sum = 0; dfs1(i, 0); k += sum - 1; } } /*计算已经赋值的边连通块里的条数*/ memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; i++) { if(!vis[i]) { sum = 0; dfs2(i, 0); k -= (sum - 1); } } ll ans = 1; for (int i = 1; i <= k; i++) ans = ans * 2 % mod; printf("%lld\n", ans); }
```