Files
python/TangDou/AcWing/DCC/SP2878.cpp
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

109 lines
4.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 1e6 + 10;
// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int g[N][N]; // 记录任意两个节点之间的憎恨关系
int n, m; // n个节点,m个关系,本题不是m条边,因为建的是补图
int color[N]; // 黑白染色法标识的颜色
int st[N]; // 是不是点双中的节点
int res[N]; // 标识数组,出现在奇数环的点双中,就是1,没有出现过就是0
// 二分染色法
int dfs(int u, int fa) {
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == fa || !st[v]) continue; // 必须是点双里的点,才能搜索,要不你搜索的就不对啦,看明白这个st数组的意义的吧~
if (color[v] == -1) { // 如果v点未染色
color[v] = color[u] ^ 1; // 反向黑白染色
if (dfs(v, u)) return 1; // 继续探索v节点,如果v节点完成后,发现了冲突,则u也汇报冲突
} else if (color[v] != (color[u] ^ 1)) // 如果已染色,并且,存在染色冲突
return 1; // 报告冲突
}
return 0;
}
// Tarjan算法,求点双
int dfn[N], low[N], stk[N], top, ts;
vector<int> bcc[N];
int bcnt;
void tarjan(int u, int fa) {
dfn[u] = low[u] = ++ts;
stk[++top] = u;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) { // 出现点双
// 业务需要,初始化临时变量数组
memset(st, 0, sizeof st); // 一个点双一清空,防止误判
memset(color, -1, sizeof color); // 黑白染色的标识数组也清空
// 利用栈内元素弹出,可以获取此环中有哪些节点,对节点标识已访问,并且计数
int x;
bcnt++;
do {
x = stk[top--];
bcc[bcnt].push_back(x);
st[x] = 1;
} while (x != v); // 将子树出栈
bcc[bcnt].push_back(u); // 把割点/树根也丢到点双里
st[u] = 1; // 将当前点双之中的全部打上标记
color[u] = 0; // u标识为黑色,0:黑,1:白
// 二分图染色
// ① 环中节点数量必须大于等于3
// ② 二分图染色存在冲突=>是奇数环
// ③ 无向图进行黑白染色,必须设置fa参数,防止走回头路
// ④ 枚举每个点双中的节点,标识已节点出现在奇数环中
if (bcc[bcnt].size() >= 3 && dfs(u, -1))
for (int i : bcc[bcnt]) res[i] = 1;
}
} else
low[u] = min(low[u], dfn[v]);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("SP2878.in", "r", stdin);
#endif
while (scanf("%d %d", &n, &m) && m + n) {
memset(g, 0, sizeof g); // 多组测试数据,每次清空憎恨数组
memset(h, -1, sizeof h); // 初始化链式前向星
idx = 0;
memset(dfn, 0, sizeof dfn); // Tarjan算法要测试多轮,清空数组【时间戳数组】
memset(low, 0, sizeof low); // Tarjan算法要测试多轮,清空数组【回溯祖先数组】
memset(res, 0, sizeof res); // 清空是否在奇数环中的标识数组
// 读入m条憎恨关系,注意:不要直接建图!因为最终的摆放方式不是憎恨的相邻摆放,而是不憎恨的相邻摆放!
while (m--) {
int a, b;
scanf("%d %d", &a, &b);
g[a][b] = g[b][a] = 1;
}
// 建立补图
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++) // i<j,这样循环次数少,一次干两事~
if (!g[i][j]) add(i, j), add(j, i); // 无向图
// 跑Tarjan算法,缩点,每个点都是一个点双连通分量
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i, -1); // 可能会不连通,找点双
// 计算未被加入到任何一个奇数环中的人员有多少个
int ans = 0;
for (int i = 1; i <= n; i++) ans += (res[i] == 0); // 加上不被点双标记的
printf("%d\n", ans);
}
return 0;
}