P6954 [NEERC 2017] Connections 题解

题目链接

我的博客

前言

本篇总结:清空!

思路

因为删边之后还需要保证所有强连通关系不变,所以我们可以想到所有强连通分量之间的边一定可以删除。

接下来我们考虑如何删除保留一个强连通分量里面的边。

对于有向图来说,\(n\) 个结点强连通需要保证有不超过 \(2n\) 条边。

因此可以在一个强连通分量里面随意找一个点,对它跑一遍正图,一遍反图。(即跑内向树和外向树),保留所有树边。

最后如果删除的边还不满 \(2n\) 条,那么随便选没有被保留的边,直到满 \(2n\) 条边。

警示后人

多测一定要全部清空啊!!!包括 tarjan 的各种数组,最终的标记数组,以及存边的数组!

代码

const int N=1e5+10;
int T,n,m;
int out[N],tot;
//out:是(0)否(1)输出 i 边
//tot:保留的边的个数
struct edge{
	int nxt[N],to[N];
	int head[N],num_Edge=0;
	void add_Edge(int from,int t){
		nxt[++num_Edge]=head[from];
		to[num_Edge]=t;
		head[from]=num_Edge;
	}
	void clear(){//清空!
		for(int i=0;i<N-5;i++) head[i]=0;
		num_Edge=0;
	}
}e1,e2;
//e1:正向图,e2:反向图

//tarjan板子
int dfn[N],low[N],dfscnt=0;
int scc[N],sc=0,st[N],top=0;
bool vis[N];
//以上的数组都需要清空!!!
void tarjan(int u){
	dfn[u]=low[u]=++dfscnt;
	st[++top]=u;vis[u]=1;
	for(int i=e1.head[u];i;i=e1.nxt[i]){
		int v=e1.to[i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		sc++;
		while(st[top]!=u&&top){
			scc[st[top]]=sc;
			vis[st[top]]=0;
			top--;
		}
		scc[st[top]]=sc;
		vis[st[top]]=0;
		top--;
	}
}
//内向树
void dfs1(int u){
	vis[u]=1;
	for(int i=e1.head[u];i;i=e1.nxt[i]){
		int v=e1.to[i];
		if(vis[v]) continue;
		if(scc[v]==scc[u]) {
			out[i]=1;
			dfs1(v);
		}
	} 
}
//外向树
void dfs2(int u){
	vis[u]=1;
	for(int i=e2.head[u];i;i=e2.nxt[i]){
		int v=e2.to[i];
		if(vis[v]) continue;
		if(scc[u]==scc[v]) {
			out[i]=1;
			dfs2(v);
		}
	}
}
void init(){
	e1.clear();e2.clear();
	for(int i=0;i<N-5;i++) out[i]=0;
	tot=0;	
	for(int i=1;i<=n;i++){
		dfn[i]=low[i]=0;
		scc[i]=0;
	}
    //尤其不要忘了这三个变量清空!
	sc=0;
	dfscnt=0;
	top=0;
}
void solve(){
	init();
	n=Read();m=Read();
	for(int i=1;i<=m;i++){
		int x=Read(),y=Read();
		e1.add_Edge(x,y);
		e2.add_Edge(y,x);
	}
	for(int i=0;i<N-5;i++) vis[i]=0;//每次用之前清空!!
	for(int i=1;i<=n;i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=0;i<N-5;i++) vis[i]=0;
	for(int i=1;i<=n;i++){
		if(!vis[i]) dfs1(i);
	}
	for(int i=0;i<N-5;i++) vis[i]=0;
	for(int i=1;i<=n;i++){
		if(!vis[i]) dfs2(i);
	}
	for(int i=1;i<=m;i++) if(out[i]) tot++;
	for(int i=1;i<=m;i++){
		if(out[i]) continue;
		if(tot<2*n) out[i]=1,tot++;//如果不满 2n 条边
	}
//	puts("----");
	for(int i=1;i<=m;i++){
		if(out[i]) continue;
		printf("%d %d\n",e2.to[i],e1.to[i]);
	}
}
signed main(){
	T=Read();
	while(T--){
		solve();//多测函数好!
	}
	return 0; 
} 
posted on 2025-11-07 20:04  _Liuliuliuliuliu  阅读(1)  评论(0)    收藏  举报