拓扑排序
最近愈发觉得自己 acm 要寄,没心情学新东西,就稍微水了点拓扑排序中的简单题,记录一下题解
拓扑排序简述
定义
拓扑排序要解决的问题是给一个图的所有节点排序。
我们可以拿大学选课的例子来描述这个过程,比如学习大学课程中有:单变量微积分,线性代数,离散数学概述,概率论与统计学概述,语言基础,算法导论,机器学习。当我们想要学习 算法导论 的时候,就必须先学会 离散数学概述 和 概率论与统计学概述,不然在课堂就会听的一脸懵逼。当然还有一个更加前的课程 单变量微积分。这些课程就相当于几个顶点 u, 顶点之间的有向边 (u,v) 就相当于学习课程的顺序。显然拓扑排序不是那么的麻烦,不然你是如何选出合适的学习顺序。下面将介绍如何将这个过程抽象出来,用算法来实现。
但是如果某一天排课的老师打瞌睡了,说想要学习 算法导论,还得先学 机器学习,而 机器学习 的前置课程又是 算法导论,然后你就一万脸懵逼了,我到底应该先学哪一个?当然我们在这里不考虑什么同时学几个课程的情况。在这里,算法导论 和 机器学习 间就出现了一个环,显然你现在没办法弄清楚你需要学什么了,于是你也没办法进行拓扑排序了。因而如果有向图中存在环路,那么我们就没办法进行 拓扑排序 了。
因此我们可以说 在一个 DAG(有向无环图) 中,我们将图中的顶点以线性方式进行排序,使得对于任何的顶点 u 到 v 的有向边 (u,v), 都可以有 u 在 v 的前面。
还有给定一个 DAG,如果从 i 到 j 有边,则认为 j 依赖于i 。如果 i 到 j 有路径(i 可达 j),则称 j 间接依赖于 i。
拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。
应用
一、P6145 [USACO20FEB]Timeline G
传送门:P6145 [USACO20FEB]Timeline G
对于每个三元组 (a,b,x),从 a 向 b 连一条长度为 x 的边,同时新建一个超级源点 0,从 0 向 i 连长度为 S[i] 的边。容易发现这张图是一个有向无环图,拓扑排序递推即可
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct node{
int v, w, nxt;
}e[maxn];
int n, m, c, cnt;
int s[maxn], indu[maxn], head[maxn];
queue<int> q;
inline void add(int u, int v, int w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].nxt = head[u];
head[u] = cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m >> c;
for (int i = 1; i <= n; i++) cin >> s[i];
for (int i = 1; i <= c; i++) {
register int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
indu[v]++;
}
for (int i = 1; i <= n; i++)
if (indu[i] == 0) q.push(i);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v, w = e[i].w;
s[v] = max(s[v], s[u] + w);
indu[v]--;
if (indu[v] == 0) q.push(v);
}
}
for (int i = 1; i <= n; i++) cout << s[i] << endl;
return 0;
}
二、P4316 绿豆蛙的归宿
传送门:P4316 绿豆蛙的归宿
设 f[x] 表示 x 点到终点的期望路径总长度,显然有 f[n] = 0,那么对于一条 x->y 的有向边,有 f[x]=sigma(f[y]+w[i])/degree[x],其中 degree[x] 表示 x 点的出度,w[i] 表示这条边的边权,那么假设我们已经知道了 f[y],就可以反推 f[x],所以只需要反向建边之后跑个拓扑排序就行了,那么最后答案即为 f[1]
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct node{
int v, w, nxt;
}e[maxn * 3];
int n, m, cnt;
int head[maxn], indu[maxn], num[maxn];
double dp[maxn];
queue<int> q;
inline void add(int u, int v, int w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].nxt = head[u];
head[u] = cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
register int u, v, w;
cin >> u >> v >> w;
add(v, u, w);
indu[u]++; num[u]++;
}
q.push(n); dp[n] = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v, w = e[i].w;
dp[v] += (dp[u] + w) / num[v];
indu[v]--;
if (indu[v] == 0) q.push(v);
}
}
printf("%.2lf", dp[1]);
return 0;
}
三、P1038 [NOIP2003 提高组] 神经网络
需要注意的一点是初始层的阈值不可以减去
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
struct node{
int v, w, nxt;
}e[maxn * 3];
int n, m, u, cnt;
int head[maxn], vis[maxn], c[maxn], num[maxn];
queue<int> q;
inline void add(int u, int v, int w) {
e[++cnt].v = v;
e[cnt].w = w;
e[cnt].nxt = head[u];
head[u] = cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> c[i] >> u;
if (c[i] > 0) {
q.push(i);
vis[i] = 1;
} else c[i] -= u;
}
for (int i = 1; i <= m; i++) {
register int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
num[u] = 1;
}
while (!q.empty()) {
int u = q.front(); q.pop();
if (c[u] < 0) continue;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v, w = e[i].w;
c[v] += c[u] * w;
if (vis[v] == 0) {
q.push(v);
vis[v] = 1;
}
}
}
bool flag = false;
for (int i = 1; i <= n; i++)
if (num[i] == 0 && c[i] > 0) {
cout << i << " " << c[i] << endl;
flag = true;
}
if (!flag) cout << "NULL" << endl;
return 0;
}