AcWing3696. 构造有向无环图

文章介绍了如何利用给定的边构建有向无环图(DAG),并进行了拓扑排序。首先定义了有向无环图的概念,然后阐述了拓扑排序的条件和其在判断图是否有环中的作用。接着,通过邻接表数据结构和拓扑排序算法,展示了如何检查和构造DAG。最后,提供了C++代码示例来实现这一过程。
摘要由CSDN通过智能技术生成

先看题:

 首先来看一下题目的要求:利用给定的边来构造一个有向无环图。

那么什么是有向无环图呢?我们来搜索一番:"所谓有向无环图其实就是:有方向的边;这些边在一个图中不会构成一个闭合的环路。"然后我们又发现:拓扑排序是对DAG的顶点进行排序,使得对每一条有向边(u, v),均有u(在排序记录中)比v先出现。亦可理解为对某点v而言,只有当v的所有源点均出现了,v才能出现。

也就是说,对样例中的图进行拓扑排序的判断,如果发现这个图能根据给出的有向边进行拓扑排序,说明这个图是有向无环图,否则就肯定不是有向无环图(有环存在)。

 

如果只考虑有向边,发现这个图就不能拓扑排序了,那么我们直接输出NO。为什么呢?因为现有的有向边中都存在环,那么无论对无向边进行如何的处理,都不会把这个环消掉,所以这个图仍然是一个有环的图。

 

但是如果当前的有向边构造了一个DAG,那么在剩余的无向边的基础上我们能够保证一定构造出一个DAG吗?答案是可以的。因为在有向边的基础上,我们已经构造出了一个DAG,也就是说知道了这个DAG的拓扑排序方式,那么自然知道了每个节点在拓扑排序中的先后位置。只要保证对每条有向边,都可以从拓扑排序中靠前的节点指向靠后的节点,那么就一定能保证不产生环,构造得到一个DAG。

 

好了,思路理清了,我们开始写代码吧。

之前写拓扑排序都是用vector写,这次看了y总的代码,直接用数组写的,我就照搬了。然后把代码中可能看不明白的解释一下。

首先,对于图的数据结构,我们使用了邻接表的结构,原来一般采用邻接矩阵,邻接矩阵虽然比较简单,但是空间复杂度比较高。

邻接表是一个存储了所有图中节点的数组,然后数组中的每个元素对应和当前节点相邻的节点组成的。

 

我们用h[i]表示第i个节点在邻接表中对应的头节点,e[i]代表的是第i条边对应的被指向的节点(比如:第三条边是a->b,那么e[3] = b.)ne[i]代表的是当前节点的下一个节点,相当于链表中next指针指向的节点,idx是每条边的索引(虽然这么说,但是反映到邻接表中就是每个节点的索引)。

这样好像明白点了。

拓扑排序用的数组模拟队列,但是还比较简单,就画个草图,不详细写了。

 

然后记得用pos记录拓扑排序中每个节点的顺序,在为无向边添加方向的时候,从靠前的节点指向靠后的节点。

代码如下:

#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
//#define ll long long
const int N = 200010, M = 200010;
int n, m, k;
//h是邻接表的表头,e是存储每条边对应的节点,ne是当前节点的下一个节点,idx是节点的索引 
int h[N], e[M], ne[M], idx;   
int d[N];
struct Edge{
	int a, b;
}edges[M]; //存储每条边 
int q[N], pos[N];   //进行拓扑排序的队列,以及每个点在排序中的位置
void add(int a, int b){  //插入一条从a->b的有向边 
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
} 
bool topsort(){
	int hh = 0;
	int tt = -1;
	for(int i = 1; i <= n; i++){
		if(!d[i]){
			q[++tt] = i;
			pos[i] = tt;
		}
	}
	while(hh <= tt){  //遍历队列,hh是队头,tt是队尾 
		int t = q[hh++];
		for(int i = h[t]; i != -1; i = ne[i]){
			int j = e[i];  //其实就是相当于查找这条边指向的节点 
			if(--d[j] == 0){
				q[++tt] = j;
				pos[j] = tt;
			}
		}
	}
	return tt == n - 1;  //tt是从-1开始的 
}
int main(){
	int t;
	scanf("%d", &t);
	while(t--){
		scanf("%d%d", &n, &m);
		idx = 0;
		memset(d, 0, (n + 1) * 4);  //int四个字节,如果用sizeof容易超时 
		memset(h, -1, (n + 1) * 4); 
		for(int i = 0; i < m; i++){
			int t, x, y;
			scanf("%d%d%d", &t, &x, &y);
			if(t) {
				add(x, y);
				++d[y];
			}
			edges[i] = {x, y};   //先把无向边存上 
		}
		if(!topsort()) puts("NO");
		else{
			puts("YES");
			for(int i = 0; i < m; i++){
				int a = edges[i].a;
				int b = edges[i].b;
				if(pos[a] < pos[b]) printf("%d %d\n", a, b);  //从靠前的点指向靠后的点 
				else printf("%d %d\n", b, a);
			}
		}
	} 
	
	return 0;
} 

 ok,就这么多,着急给我妈打电话去了。

借鉴的题解(其实是代码来源):

AcWing 3696. 构造有向无环图【DAG + 拓扑排序】 - AcWing

AcWing 3696. 构造有向无环图(AcWing杯 - 周赛) - AcWing

https://www.cnblogs.com/en-heng/p/5085690.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值