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.

102 lines
3.5 KiB

2 years ago
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, M = 2e6 + 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 dfn[N], low[N], stk[N], top, timestamp;
vector<int> bcc[N];
int bcnt;
unordered_set<int> in;
int n, m;
int ans1, ans2, ans3;
void deal_circle() {
int sum = 0;
for (int u : bcc[bcnt])
for (int j = h[u]; ~j; j = ne[j])
if (in.count(e[j])) sum++; //此边的两个点都在点双中,则此边在点双中
sum /= 2; //因为a->b,b->a都sum++,重复了需要除以2,才是边数
//边数大于点数,嵌套环,每条边都是冲突边
if (sum > bcc[bcnt].size()) ans2 += sum;
//判定割边 (第二种判定割边的办法)
//此处写ans3+=sum 或 ans3+=1是一个意思因为此时只能是两点一线的情况否则就不是点双。特殊的点双只有一条割边
if (sum < bcc[bcnt].size()) ans3 += sum;
}
void tarjan(int u, int fa) {
low[u] = dfn[u] = ++timestamp;
stk[++top] = u;
int son = 0; //子节点个数
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
if (!dfn[j]) {
son++; //找到一个子节点
tarjan(j, u);
low[u] = min(low[u], low[j]);
//第一种判定割边的办法
//参阅一下判割边的模板代码
if (low[j] > dfn[u]) ans1++;
if (low[j] >= dfn[u]) {
int x;
bcnt++;
//针对每个点双而言都在dfs过程中记录此点双中有哪些点避免后期二次统计造成速度下降TLE
in.clear();
do {
x = stk[top--];
bcc[bcnt].push_back(x);
in.insert(x); //增加点双中有哪些点的记录信息
} while (x != j);
bcc[bcnt].push_back(u);
in.insert(u); //增加点双中有哪些点的记录信息
deal_circle(); //在完成点双的记录后马上开始统计以免将所有的点双中点的信息完整记录下来造成MLE,TLE等问题
}
} else
low[u] = min(low[u], dfn[j]);
}
//特判独立点
if (fa == -1 && son == 0) bcc[++bcnt].push_back(u);
}
int main() {
//文件输入输出
#ifndef ONLINE_JUDGE
freopen("HDU3394.in", "r", stdin);
#endif
while (~scanf("%d%d", &n, &m) && n + m) {
// ans1:割边数量 ans2:冲突边数量,也就是点双中所有边的数量,它们都是存在多个环中 ans3:也是割边的数量,与 ans1相同但实现的方式不一样
ans1 = ans2 = ans3 = idx = top = timestamp = 0;
memset(dfn, 0, sizeof dfn);
memset(h, -1, sizeof h);
// memset(low, 0, sizeof low); //low数组不用重新初始化
while (m--) {
int a, b;
scanf("%d %d", &a, &b);
a++, b++; //避开下标0向右推一个使得下面的 tarjan算法的模板不变
if (a != b) add(a, b), add(b, a); //防止重边
}
// tarjan求点双的模板
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i, -1);
//下面无论是取 ans1还是ans3都是一样的
printf("%d %d\n", ans1, ans2);
// printf("%d %d\n", ans3, ans2);
}
return 0;
}