无向图的桥

https://www.luogu.com.cn/problem/P1656
题意:给一张联通的无向图,问是否存在边,去掉它就不是全联通了,输出边的端点

input

6 6
1 2
2 3
2 4
3 5
4 5
5 6

output

1 2
5 6

思路:

用tarjan处理出每个点的low和dfn,对于一条边(u->j),如果low[j] > dfn[u],表明j点除了通过u点(父亲),无法到达u及以前的点,把它去掉就行了.
但是因为是无向图,所以需要用sign[i]表示i边是否走过,有个技巧是,一条无向边的去和来,对应的是i和i^1.

代码

/*
belong[i]表示i在第几个连通分量里
dfn[i]表示i点在搜索时的时间戳
low[i]表示i或i的子树能回溯到的最早的点的dfn
*/
int n,m;
int h[N],e[M],ne[M],idx;
int low[N],dfn[N],stk[N],top,belong[N],times;
bool instk[N];
int scc,size[N];   //强连通分量的个数和大小
bool sign[M]; 

void add(int a,int b) {
	e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

struct node {
	int x,y;
} ans[M];
int ww;

bool cmp(node a,node b) {
	if(a.x != b.x)return a.x < b.x;
	return a.y < b.y;
}

void tarjan(int u) {
	low[u] = dfn[u] = ++times;
	stk[top++] = u;
	instk[u] = true;
	for(int i=h[u]; ~i; i=ne[i]) {
		if(!sign[i]) {
			sign[i] = sign[i^1] = true;  //标记是否走过
			int j = e[i];
			if(!dfn[j]) {
				tarjan(j);
				low[u]=min(low[u],low[j]);
				
				if(dfn[u] < low[j]) {   //核心 
					ans[ww].x = min(u,j);
					ans[ww++].y = max(u,j);
				}
			} else if(instk[j])low[u] = min(low[u],dfn[j]);
		}
	}
	if(low[u] == dfn[u]) {
		scc++;
		int v;
		do {
			v = stk[--top];
			instk[v] = false;
			belong[v] = scc;
			size[scc]++;
		} while(v != u);
	}
}

void work() {
	n=rd(),m=rd();
	memset(h,-1,sizeof(h));
	while(m--) {
		int a=rd(),b=rd();
		add(a,b);add(b,a);
	}
	memset(dfn,0,sizeof(dfn));
	memset(instk,false,sizeof(instk));
	times = top = scc = 0;
	idx = 0;
	ww = 0;
	
	for(int i=1; i<=n; i++) {
		if(!dfn[i])tarjan(i);
	}
	
	sort(ans,ans+ww,cmp);
	for(int i=0; i<ww; i++) {
		printf("%d %d\n",ans[i].x,ans[i].y);
	}
}

int main() {
	freopen("in.txt","r",stdin);
	work();
	return 0;
}
posted @ 2021-04-19 20:05  LaiYiC  阅读(125)  评论(0)    收藏  举报