Codeforces Round 997 B 题——一道类拓扑排序题
哈喽大家好,时隔两个月今天又来写算法题了。(https://codeforces.com/contest/2056/problem/B)
这次甲方给了我们一张无向图

要求我们找出一个排列序p,满足在任取两个p中的数p[i],p[j];(i < j)(顶点),若p[i] < p[j],则图中一定存在一条边<i,j>。
换句话说,如果p[i] > p[j],那图中一定不存在这条边。
无向图看起来抽象,我们先把它转化为有向图,只保留大顶点指向小顶点的边。这样,每一条边(i->j)就代表在p中i在j后面。

在这张图中,最终排列p里1,2在3之前,那么4,5一定在3之后,我们也把他连接上。
最终得到一张有向图,我们对它求拓扑序列即可
步骤总结:把邻接矩阵左下角保留,右上角01互换,求出拓扑序列即可。
vector<int> topologicalSort(const vector<vector<bool>> &adjMatrix) {
size_t n = adjMatrix.size();
vector<int> inDegree(n, 0);
vector<int> result;
// 计算每个节点的入度
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (adjMatrix[i][j] != 0) {
++inDegree[j];
}
}
}
// 使用队列存储入度为0的节点
queue<int> zeroInDegree;
for (int i = 0; i < n; ++i) {
if (inDegree[i] == 0) zeroInDegree.push(i);
}
// 开始拓扑排序
while (!zeroInDegree.empty()) {
int node = zeroInDegree.front();
zeroInDegree.pop();
result.push_back(node);
// 遍历当前节点的所有邻居
for (int j= 0; j < n; ++j) {
// 断开和邻居的边
if (adjMatrix[node][j] != 0) {
--inDegree[j];
// 将入度为0的邻居添加到队列中。
if (inDegree[j] == 0) zeroInDegree.push(j);
}
}
}
// 如果排序结果的大小不等于节点数,说明图中存在环,无法拓扑排序
if (result.size() != n) {
dbg("ring!")
return {};
}
return result;
}
inline void solve_b1() {
/**
* 题意:给你一张无向图,请你找到一个排列序列p,满足:任取两个顶点为i,j,若p[i] < p[j],则边<p[i],p[j]>一定存在,反之一定不存在。
* 思路:只需要找到每个数,比它小的数个数是多少即可。
* 换句话说,对于任取两个顶点为i,j,若p[i] < p[j],则边<p[i],p[j]>一定存在,若p[i] < p[j],则边一定不存在。
* 我们可以先将无向图转为有向图:保留大的顶点指向小的顶点的边。例如有一条边(5->3)这样就代表在答案p中,3在5前面。
* 其次,例如图中不存在边<2,1>,这样就代表2在1前面。也就是说,我们可以构造一条边(1->2)
* 总结操作步骤:把邻接矩阵左下角保留,右上角01互换,求出拓扑序列即可。
*/ size_t n;
cin >> n;
// 邻接矩阵
vector<vector<bool>> matrix(n, vector<bool>(n, false));
// 输入数据
// dbg(n)
for (int i = 0; i < n; ++i) {
string s;
cin >> s;
// dbg(s);
for (int j = 0; j < n; ++j) {
matrix[i][j] = s[j] & 0xf;
// dbg((matrix[i][j]))
}
}
// cout << "???\n";
// 将右上角01互换
for (int r = 0; r < n; ++r) {
for (int c = r+1; c < n; ++c) {
matrix[r][c] = !matrix[r][c];
}
}
vector<int> res = topologicalSort(matrix);
std::for_each(res.rbegin(), res.rend(), [](int x) {cout << x+1 << ' ';});
cout << endl;
}
但但但是,经过我们上面的操作,这个图已经由疏关系图转为密关系图了。密关系图就是任取图中两个顶点i,j,总会存在一条边(i->j)或(j->i),且不会同时存在这两条边,如果同时存在则是关系不明图,也就是成环图。
拓扑排序的本质就是对一些存在比较关系的元素(顶点)进行排序,这些比较关系可以构成一张图,任意两个顶点直接可能存在比较关系,也可能不存在比较关系,因此拓扑排序结果是不唯一的。但是密关系图拓扑排序结果是唯一的。
例如我们想对一个数组[3,4,1,5]进行排序(假设从小到大),它的结果是唯一的,因为任意两个数字我们都可以确定他的大小关系。
同理对于这道题转化后的图,任取一个顶点,它的出度是多少那么最中答案p中在它前面就有多少个顶点,也就能直接确定它的位置了。
inline void solve_b2() {
/**
* 题意:给你一张无向图,请你找到一个排列序列p,满足:任取两个顶点为i,j,若p[i] < p[j],则边<p[i],p[j]>一定存在,反之一定不存在。
* 思路:只需要找到每个数,比它小的数个数是多少即可。
* 换句话说,对于任取两个顶点为i,j,若p[i] < p[j],则边<p[i],p[j]>一定存在,若p[i] < p[j],则边一定不存在。
* 我们可以先将无向图转为有向图:保留大的顶点指向小的顶点的边。例如有一条边(5->3)这样就代表在答案p中,3在5前面。
* 其次,例如图中不存在边<2,1>,这样就代表2在1前面。也就是说,我们可以构造一条边(1->2)
* 总结操作步骤:把邻接矩阵左下角保留,右上角01互换,求出拓扑序列即可。
*/ size_t n;
cin >> n;
// 邻接矩阵
vector<vector<bool>> matrix(n, vector<bool>(n, false));
// 输入数据
// dbg(n)
for (int i = 0; i < n; ++i) {
string s;
cin >> s;
// dbg(s);
for (int j = 0; j < n; ++j) {
matrix[i][j] = s[j] & 0xf;
// dbg((matrix[i][j]))
}
}
// cout << "???\n";
// 将右上角01互换
for (int r = 0; r < n; ++r) {
for (int c = r + 1; c < n; ++c) {
matrix[r][c] = !matrix[r][c];
}
}
vector<int> p(n, 0);
for (int r = 0; r < n; ++r) {
int outDegree = 0;
for (int c = 0; c < n; ++c) {
outDegree += matrix[r][c];
}
p[outDegree] = r + 1;
}
std::for_each(p.begin(), p.end(),[](int x){cout << x << ' ';});
cout << endl;
}

浙公网安备 33010602011771号