CF962F Solution
题解
本题需要求出所有简单环所覆盖的边。可以发现所有简单环均为点双联通分量,因此可以求出所有点双连通分量,判断它们是否为环。求点双连通分量可以用tarjan,而环的边数一定等于点数。但是本题需要输出边,而一般tarjan中所求为点双中的点,因为一个点可以同时属于多个点双,使得找出点后反推边变得十分麻烦(题解作者写挂了QAQ),但以边来推点却较为简单。因此直接在栈中记录点双中的边,每当发现一个点双,统计其点数与边数,如果相等则将其中的边加入答案序列即可。Tips:需要用\(vis\)数组记录每条双向边是否已经入栈过,否则点双最后的返祖边会被重复进栈(因为第一次找点双时其并不在遍历过程中真的经过)。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=2100010;
int fst[N],nxt[2*M],v[2*M],id[2*M],cnt=1;
int dfn[N],low[N],num;
int st[N],top,ans[N],tot;
bool vis[N];
set<int> qwq,qaq;//qwq:当前点双中的点,qaq:当前点双中的边
set<int>::iterator it;
void add(int x,int y,int z)
{
v[++cnt]=y,id[cnt]=z;
nxt[cnt]=fst[x],fst[x]=cnt;
}
void tarj(int x)
{
dfn[x]=low[x]=++num;
for(int i=fst[x];i;i=nxt[i])
{
int y=v[i];
if(vis[id[i]]) continue;
st[++top]=i; vis[id[i]]=1;
if(!dfn[y])
{
tarj(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
qwq.clear(),qaq.clear();
qwq.insert(x);
int pos=0;
while(pos!=i)
{
pos=st[top--];
qwq.insert(v[pos]);
qaq.insert(id[pos]);
}
if(qaq.size()==qwq.size())
for(it=qaq.begin();it!=qaq.end();it++) ans[++tot]=*it;
}
}
else low[x]=min(low[x],dfn[y]);
}
}
int main()
{
int n,m,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y,i),add(y,x,i);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) {num=0; tarj(i);}
printf("%d\n",tot);
sort(ans+1,ans+tot+1);
for(int i=1;i<=tot;i++) printf("%d ",ans[i]);
return 0;
}