题目大意:
给定一个由有向边和无向边组成的图,要求你将所有无向边变成有向边,使得图中不出现环。
由于题目没有说明给定图是否有解,所以我们还需要判断不存在解的情况。
那么什么情况会不存在解呢?
很显然,如果原图给定的有向边已经形成环了,那么说明此时必定是无解的。说明我们只需要判断原图是否有环就可以判断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;
}