2022.10.14 总结
1. 逐月 P8006
题意
小 \(Q\) 有 \(n\) 颗珍珠,每颗珍珠可以是红色或者蓝色。
小 \(L\) 会问 \(m\) 个问题,每个问题都是:编号为 \(x\) 的珍珠和编号为 \(y\) 的珍珠颜色是否相同。如果答案为 \(0\),则说明不同,如果答案为 \(1\),则说明一样。
现在小 \(L\) 想知道小 \(Q\) 是否一定说了谎,如果不一定,他还想知道有多少种可行的方案。
思路
100 分
我们可以将珍珠视为点,问题视为边,所以,这个题目就变成了一道模板的 二分图染色 了。
先介绍一下二分图:
对于每一个点,都可以染成黑色或者白色,存在一种方案,使得任意相邻两个点的颜色都不同。
所以,这就是一个图的深搜(耶!)
那么,什么时候这个图是不合法的呢?
就是,当这个点已经被遍历过了,并且,它的颜色和它应该是的颜色不同,就说明,这个图是不合法的。
但是,要怎么求方案数呢?
先说结论,令 \(cnt\) 为连通块数量,那么方案数就是 \(2 ^ {cnt}\)。
因为,对于每个连通块,可以把它们分成两个部分,第一个部分的点颜色为 \(1\),第二个部分的点颜色为 \(2\),\(1\) 和 \(2\) 又不可以相等,所以每个连通块都有两种涂色方法。
所以方案数是 \(2 ^ {cnt}\)。
然后,事情就变得简单了,直接做图的遍历即可。
时间复杂度
图的遍历,点数 + 边数,\(O(n + m)\)。
空间复杂度
邻接表,\(O(m)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 300010, MOD = 998244353;
int n, m, tp, x, y, w, cnt, v[N];
bool flag = 1;
struct C{
int d, w;
};
vector<C> f[N];
void dfs(int t, int co){ // 二分图染色
if (v[t] != -1) {
flag &= (v[t] == co);
return ;
}
v[t] = co;
for (int i = 0; i < f[t].size(); i++) {
dfs(f[t][i].d, f[t][i].w ^ co);
}
}
int main(){
freopen("pearl.in", "r", stdin);
freopen("pearl.out", "w", stdout);
cin >> n >> m >> tp;
fill(v + 1, v + n + 1, -1);
for (int i = 1; i <= m; i++) {
cin >> x >> y >> w;
f[x].push_back({y, !w}), f[y].push_back({x, !w}); // 邻接表
}
for (int i = 1; i <= n; i++) {
if (v[i] == -1) {
cnt++;
dfs(i, 0); // 搜索连通块
}
}
if (!flag) {
if (tp == 1) {
cout << "NO";
} else {
cout << 0;
}
} else {
if (tp == 1) {
cout << "YES";
} else {
long long ans = 1;
for (int i = 1; i <= cnt; i++) {
ans = ans * 2 % MOD;
}
cout << ans;
}
}
return 0;
}