Codeforces Round #656 (Div. 3) E. Directing Edges(补题)

题意:给一个图,有些是有向的有些是无向的。要求将无向的标成有向的,并且让整个图不形成环。如果可以输出YES并给出其中一种方案。否则输出NO。

做不出来的原因:不会拓扑排序。思考的时候想到过使用并查集但是被一个点连着所有点的有向给否定了。其实有一个拓扑排序的意思,就是知道点的前后顺序就行了。但是还是太菜了。

拓扑排序

拓扑排序的算法是:将有向图的入度为0的点去掉,然后去掉它所连接的边,然后先去的排在前面,后去的排在后面,直到全部去除完毕。形成的排序就叫做拓扑排序。

如果去除到最后发现仍然有点,说明有环,否则应该能去除所有的点。

实现

首先用vector维护图,放入以后用数组记录一下每个点的入度。结束后遍历数组,记录是否有点入度为0,是的话就放入队列。然后从队列出发,每次push一个点出来,并将和这个点相连接的点的入度--,如果相连接的点已经为0了,放入队列即可。直到队列为空。此时若是已经遍历了所有的点,则说明无环,否则有环。

 

本题根据有向完成拓扑排序后,只需要将无向再处理一下。按拓扑排序的顺序从小到大即可(因为这样就不会影响拓扑排序的顺序,从而说明仍然可以按照原来的拓扑排序去走。因为你把边放在了从前到后,回忆拓扑排序过程,增加这条边不会影响之前所有点的入度,因此拓扑排序不变。而当这个点被去除后,新增加的边也被删除了,因此也不影响后面)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int edf[N],edin[N],edout[N];//用来放入无向边
int V;//点数
queue<int> q;//维护一个入度为0的顶点的队列 
vector<int> gra[N];//邻接矩阵 
int in_degree[N];//维护图中每个点的入度
int tp[N];//拓扑排序以后点所对应的顺序 
int tpflag;
void Graph(int V);                   // 构造函数
void addEdge(int v, int w);     // 添加边
bool topological_sort();        // 拓扑排序 

//初始化图 
void Graph()
{	
	//清空图 
	for (int i=0;i<V;i++)
		gra[i].clear(); 
	//清空入度数组 
	memset(in_degree,0,sizeof(in_degree));
	//注意入度为0的队列不需要清空每次都会自己计算完毕  	
	tpflag=0; 
} 

//增加边
void addEdge(int v, int w)
{
	gra[v].push_back(w);
	in_degree[w]++;
} 

//拓扑排序
bool topological_sort()
{
	//遍历所有点,如果入度为0,则加入这个点 
	for (int i=0;i<V;i++)
	{
		if (in_degree[i]==0)
		{
			q.push(i);
		}
	}
	while (!q.empty()) 
	{
		int v=q.front();
		q.pop();
		tp[v]=tpflag++;//v点被标记第tpflag个值。
		for (int i=0;i<gra[v].size();i++)
		{
			//与该点连接的所有点减去一个入度 
			in_degree[gra[v][i]]--;
			//如果入度被清零了,则放入队列 
			if (in_degree[gra[v][i]]==0)
			{
				q.push(gra[v][i]);
			}
		} 
	}
	//如果遍历了所有的点,说明没有环路。否则有环路 
	if (V==tpflag) return true;
	else return false;
} 
void jjwt()
{
	memset(edf,0,sizeof(edf));
	memset(edin,0,sizeof(edin));
	memset(edout,0,sizeof(edout));
	Graph();	
	int n,m;
	scanf("%d%d",&n,&m);
	V=n;
	for (int i=0;i<m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		y--;z--;
		edf[i]=x;
		edin[i]=y;
		edout[i]=z;
		if (x) addEdge(y,z);
	}
	
	bool tmp=topological_sort();	
	if (tmp) printf("YES\n");
	else
	{
		printf("NO\n");
		return;
	} 
	
	for (int i=0;i<m;i++)
	{
		if (edf[i]||tp[edin[i]]<tp[edout[i]])
		{
			printf("%d %d\n",edin[i]+1,edout[i]+1);
		}
		else
		{
			printf("%d %d\n",edout[i]+1,edin[i]+1);
		}
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while (T--)
	{
		jjwt();
	}
	return 0;
}

代码有点长,主要有一个拓扑排序的板子占用了60多行。

 

这题还是有点意思的。主要思考问题在拓扑排序。拓扑排序就是一个经典的计算机思维:我并不要求这个结果能完全反应原图,但是它能很好的反应原图的一些信息。各个点之间要么是指向要么无关联。为了不成环怕就怕反过来,那么指向和无关联对我来说无所谓。这样就可以把图给改成一个拓扑排序了。

本小垃圾的易错点:

点是1-V,习惯性0-V-1

点和边日常性弄反,打印边的时候m写成了V,拓扑里面和点比较的时候又变成了和边比较。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值