About

有向无环图(DAG)

  • 有向图:由顶点和有向边组成的图,边具有方向性。
  • 无环图:图中不存在任何有向环路。
  • 拓扑排序:只适用于有向无环图(DAG),用于确定顶点的线性顺序,使得对于每一条有向边 (u,v)(u, v),顶点 uu 在顶点 vv 之前。

拓扑排序的定义

给定一个有向无环图 G=(V,E)G = (V, E),拓扑排序是将图中所有顶点排成一个线性序列,使得对于每一条有向边 (u,v)(u, v),顶点 uu 在顶点 vv 之前。

示例

假设有以下有向图:

1
2
3
1 -> 2 -> 3
1 -> 4
4 -> 3

一个合法的拓扑排序是:1, 2, 4, 31, 4, 2, 3


拓扑排序的原理

拓扑排序的核心思想是通过不断移除入度为 0 的顶点,并更新其邻接顶点的入度,直到所有顶点都被移除或无法继续移除。

算法步骤

  1. 初始化
    • 计算每个顶点的入度(即指向该顶点的边的数量)。
    • 将所有入度为 0 的顶点加入队列。
  2. 处理队列
    • 从队列中取出一个顶点,将其加入拓扑排序结果。
    • 遍历该顶点的所有邻接顶点,将其入度减 1。如果某个邻接顶点的入度变为 0,则将其加入队列。
  3. 结束条件
    • 如果所有顶点都被处理,则拓扑排序成功。
    • 如果队列为空但仍有未处理的顶点,则图中存在环,拓扑排序失败。

T1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//纯板子判环
#include <bits/stdc++.h>
using namespace std;
const int N = 1010,M = 2010;
struct node{
int to,nxt;
}a[M];
int pre[N],in[N];
queue <int> q;
int n,m;
int k;
void add(int u,int v){
a[++k] = {v,pre[u]};
pre[u] = k;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--){
int cnt = 0;
memset(in,0,sizeof in);
memset(pre,0,sizeof pre);
k = 0;
while(!q.empty()) q.pop();
cin>>n>>m;
for(int i = 1;i <= m;i++){
int x,y;
cin>>x>>y;
add(x,y);
in[y]++;
}
for(int i = 1;i <= n;i++){
if(in[i] == 0){
q.push(i);
cnt++;
}
}
while(!q.empty()){
int h = q.front();
q.pop();
for(int i = pre[h];i;i = a[i].nxt){
int to = a[i].to;
in[to]--;
if(in[to] == 0){
q.push(to);
cnt++;
}
}
}
bool fl = 1;
for(int i = 1;i <= n;i++) if(in[i] != 0) fl = 0;
if(fl) cout<<"N\n";
else cout<<"Y\n";
}
}

T2

  • 拓扑排序+DP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10,M = 5e4+10;
struct node{
int to,nxt;
}a[M];
int pre[N],in[N],c[N],dp[N],k;
int n,m;
void add(int u,int v){
a[++k] = {v,pre[u]};
pre[u] = k;
}
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];
}
for(int i = 1;i <= m;i++){
int x,y;
cin>>x>>y;
add(x,y);
in[y]++;
}
queue<int> q;
int cnt = 0;
for(int i = 1;i <= n;i++){
if(in[i] == 0) {
q.push(i);
cnt++;
dp[i] = c[i];
}
}
while(!q.empty()){
int h = q.front();
q.pop();
for(int i = pre[h];i;i = a[i].nxt){
//使用当前节点松弛其他节点
int to = a[i].to;
dp[to] = max(dp[to],dp[h]+c[to]);
in[to]--;
if(in[to] == 0) q.push(to);
}
}
int ans = 0;
for(int i = 1;i <= n;i++){
ans = max(ans,dp[i]);
}
cout<<ans<<endl;
}

T3

T2 Plus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
* @Author FrankWKD (wangkedi01)
* @Date 2025-06-09
* @Source https://oj.czos.cn/contest/problem?id=24562&pid=2
* @FileName T3.cpp
* @FilePath /media/frank/FrankW/_default/_Mine!/Working/code-spaces/czos/TopologicalSorting/T3.cpp
* @Note 本质上就是树上DP,优化了一下这个DP的顺序。
*/

// #pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
int in[N], out[N], x, y, n, m, pre[N], clonein[N], k;
int dp[N];
struct node {
int to, nxt;
} a[M];

void add(int u, int v) {
a[++k] = {v, pre[u]};
pre[u] = k;
}
int main() {
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
add(x, y);
in[y]++;
clonein[y]++;
out[x]++;
}
queue<int> q;
for (int i = 1; i <= n; i++) {
if (in[i] == 0) {
q.push(i);
dp[i] = 1;
}
}
while (!q.empty()) {
int h = q.front();
q.pop();
for (int i = pre[h]; i; i = a[i].nxt) {
int to = a[i].to;
dp[to] += dp[h];
in[to]--;
if (in[to] == 0) {
q.push(to);
}
}
}
int tot = 0;
for (int i = 1; i <= n; i++) {
if (clonein[i] != 0 and out[i] == 0) {
tot += dp[i];
}
}
cout << tot << endl;
return 0;
}