题意:
n个点m条边的无向图,问有哪些边在一个简单环上,按顺序输出这些边的编号
思路:
对于无向图求出每个双联通分量,对于每个双联通分量,如果点的个数==边的个数,那么这个双联通分量就是个简单环,输出这个双联通分量的所有边,否则不是
这道题如果直接搜割点是不对的,两个特殊样例如下:
- 对于样例①:割点是1,2,3,这样子的话很有可能只会找到3个双联通分量(1-4-5,2-6-7,3-8-9-10)从而将边算少
- 对于样例②:割点是3,6,但是边(3,5)和(5,6)有可能被割点从它所属的双联通分量中分离,从而将边算多
解决方法:
直接在求双联通分量时用栈维护当前的边即可,而不用求割点后DFS,具体过程看代码注释
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<map>
#include<string>
#include<math.h>
#include<queue>
#include<stack>
#include<iostream>
using namespace std;
#define LL long long
#define mod 1000000007
vector<int> G[200005], F[200005];
stack<int> st, temp;
int cnt, t, cut, low[200005], time[200005], ans[200005], vis[200005], to[200005];
void Trajan(int u, int p, int last)
{
int i, v, flag, now;
low[u] = time[u] = ++t;
for(i=0;i<G[u].size();i++)
{
v = G[u][i];
if(v==p)
continue;
//printf("%d %d\n", u, v);
if(time[v]==0)
{
to[F[u][i]] = v;
st.push(F[u][i]);
Trajan(v, u, F[u][i]);
low[u] = min(low[u], low[v]);
if(time[u]==low[v]) //u点是割点
{
cut++, flag = 1;
while(1)
{
now = st.top();
st.pop();
//printf("%d %d\n", u, now);
temp.push(now);
if(vis[to[now]]!=cut) //对于当前双联通分量中的每一条边,它们一个方向上的终点应该没有重复
vis[to[now]] = cut;
else
flag = 0;
if(now==F[u][i])
break;
}
while(temp.empty()==0)
{
if(flag)
ans[++cnt] = temp.top();
temp.pop();
}
}
}
else if(time[v]<time[u])
{
to[F[u][i]] = v;
st.push(F[u][i]);
low[u] = min(low[u], time[v]);
}
}
if(st.empty()==0 && st.top()==last) //当前边为桥
{
//printf("%d %d\n", u, last);
printf("%d\n", last);
st.pop();
}
}
int main(void)
{
int n, m, i, x, y;
scanf("%d%d", &n, &m);
for(i=1;i<=m;i++)
{
scanf("%d%d", &x, &y);
G[x].push_back(y), F[x].push_back(i);
G[y].push_back(x), F[y].push_back(i);
}
for(i=1;i<=n;i++)
{
if(time[i]==0)
Trajan(i, 0, 0);
}
sort(ans+1, ans+cnt+1);
printf("%d\n", cnt);
for(i=1;i<=cnt;i++)
printf("%d ", ans[i]);
puts("");
return 0;
}