|
|
#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;
|
|
|
} |