You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
4.6 KiB

2 years ago
#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标识为黑色01
// 二分图染色
// ① 环中节点数量必须大于等于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;
}