Codeforces Round #656 (Div. 3) E. Directing Edges(简单拓扑)

原题链接:Problem - E - Codeforces

题目大意: 

        给定一个由有向边和无向边组成的图,要求你将所有无向边变成有向边,使得图中不出现环。

        由于题目没有说明给定图是否有解,所以我们还需要判断不存在解的情况。

        那么什么情况会不存在解呢?

        很显然,如果原图给定的有向边已经形成环了,那么说明此时必定是无解的。说明我们只需要判断原图是否有环就可以判断NO的情况了。

        那么对于YES的情况呢?

        思考拓扑序,在拓扑中,我们的边永远是拓扑序小的走向拓扑序大的。

        对于这一张图:


        很容易可以看出,拓扑序是 1 - 3 - 2 - 4,而且满足不存在环的情况。

        那么如果多了无向边呢?那也没问题。

        对于这一张图,我们仍然只需要让拓扑序小的连拓扑序大的,就能满足无环。很容易想到,存在无向边不会对原有有向边图的拓扑序造成影响,那么我们就可以先不考虑无向边,将原有图的拓扑序求出。

//拓扑排序板子
//cnt代表当前点的拓扑序是多少 用一个top数组存每个点对应的排名

void topo()
{
	queue<int> que;
    //将入度为0的点放入队列
	for (int i = 1; i <= n; ++i) 
		if (!in[i]) que.push(i);

	while (que.size())
	{
		auto t = que.front(); que.pop();
		top[t] = ++cnt;
		for (auto& x : v[t])//v数组是vector实现邻接表
			if (--in[x] == 0) que.push(x);
	}

	//判断一下是否有环 也就是cnt是否等于n
	if (cnt == n) cout << "YES\n";
	else return void(cout << "NO\n");
}

        这样,就完成了有向图的拓扑排序。接下来考虑无向边,还是那个原理,我们仍然只需要让拓扑序小的连拓扑序大的,就能满足无环。

        考虑每个带无向边的点,存在无向边,那就不计算进入度,然后直接纳入进去拓扑排序求出每个边的拓扑序就行啦!

        容易知道,这样是可行的,我们能够求出所有点的拓扑序,直到最后每个点总会有一个拓扑序,我们也就可以处理无向边了。

        要达到这种效果,我们要把有向边和无向边分开存,才能跑出正确的拓扑。最后再先将所有有向边输出,再最后输出处理后的无向边就行了。直接上代码。

#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <string>
#include <iomanip>
#include <algorithm>
using namespace std;

const int N = 2e5 + 10;
using PII = pair<int, int>;

//top是拓扑序 in是入度
int n, m, cnt = 0, top[N], in[N];
vector<int> v[N];//邻接表
vector<PII> edge;//用pair存无向边对应的的两个点

//拓扑排序板子 看不懂的请复习
void topo()
{
	queue<int> que;

	for (int i = 1; i <= n; ++i) 
		if (!in[i]) que.push(i);

	while (que.size())
	{
		auto t = que.front(); que.pop();
		top[t] = ++cnt;
		for (auto& x : v[t])
			if (--in[x] == 0) que.push(x);
	}

	//判断一下是否有环 也就是cnt是否等于n
	if (cnt == n) cout << "YES\n";
	else return void(cout << "NO\n");

    //输出有向边
	for (int i = 1; i <= n; ++i)
		for (auto x : v[i])
			cout << i << ' ' << x << '\n';
    //处理无向边
	for (auto x : edge)
	{
        //处理一下每一条无向边,如果左边点拓扑序大于右边,那就交换一下
		if (top[x.first] > top[x.second]) swap(x.first, x.second);
		cout << x.first << ' ' << x.second << '\n';
	}

}

void solve()
{
	cnt = 0;
	cin >> n >> m;

	//因为是多组数据 所以要重初始化 这是个坑点 要把所有用到的都初始化一遍
	edge.clear();
	for (int i = 1; i <= n; ++i)
		v[i].clear(), top[i] = 0, in[i] = 0;

	//有向边和无向边分别用 v 和 edge 存
	for (int i = 0, t, x, y; i < m; ++i)
	{
		cin >> t >> x >> y;
		if (t) v[x].emplace_back(y), in[y]++;
		else edge.emplace_back(x, y);
	}
	topo();
}

signed main()
{

#ifndef ONLINE_JUDGE
	freopen("r.in", "r", stdin);
	//freopen("w.out", "w", stdout);
#endif

	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int t; cin >> t;
	while (t--) solve();

	//solve();

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值