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;
}
posted @ 2021-03-09 21:57  violet_holmes  阅读(160)  评论(0编辑  收藏  举报