拓扑教学题解

P4017 最大食物链计数

首先确定通过拓扑排序来解决问题,要找到最长的食物链,就要明确食物链的长度如何确定。

从入度为 0 的点开始,到达一个出度为 0 的点之后,确定一条食物链。

最终答案是将所有链的长度加合到一起,记得开 longlong 和 取模。

 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
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6, MOD = 80112002;

int n,m,in[N],out[N],ans,vis[N],a[N];
vector<int>g[N];

void TopoSort(){
    queue<int>s;
    ans=0;
    for(int i=1;i<=n;i++){
        if(!in[i]){
            vis[i]=1;
            s.push(i);
        }
    }
    while(!s.empty()){
        int u = s.front();
        s.pop();
        for(auto v:g[u]){
            vis[v]=vis[v]+vis[u]%MOD;
            if(--in[v]==0){
                s.push(v);
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(!out[i])ans=ans+vis[i]%MOD;
    }
    cout<<ans%MOD<<endl;
}

signed main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        out[u]++,in[v]++;
    }
    TopoSort();
    return 0;
}

P2712 摄像头

本题是直播中的例题

只能砸当前没被监视到的,那我们就 S 中有多少个能进来的元素,因为进 S 中的元素都是入度为 0 的元素。

坑点在于给的数据不一定连续,即可能有 3 个摄像头,但是给的摄像头对应位置是 1,3,5 这种,不能按顺序进行,要用数组来储存对应位置,并记录某些位置是否有摄像头,因为一些摄像头也可能监视到不存在摄像头的位置。

 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
#include <bits/stdc++.h>
using namespace std;

int n,m,in[10001],ans,vis[10001],a[10001];
vector<int>g[10001];

void TopoSort(){
    queue<int>s;
    ans=0;
    for(int i=1;i<=n;i++){
        if(!in[a[i]] && vis[a[i]])
            s.push(a[i]);
    }
    while(!s.empty()){
        int u = s.front();
        ans++;
        s.pop();
        for(auto v:g[u]){
            if(--in[v]==0&&vis[v])s.push(v);
        }
    }
    if(ans==n)cout<<"YES\n";
    else cout<<n-ans<<endl;
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        int m;
        cin>>a[i]>>m;
        vis[a[i]]=1;
        for(int j=1;j<=m;j++){
            int y;
            cin>>y;
            g[a[i]].push_back(y);
            in[y]++;
        }
    }
    TopoSort();
    return 0;
}

P6145 [USACO20FEB] Timeline G

首先看到题目,当然要明白它究竟想要干什么。

题目描述的方式很像一个 DAG,于是把每次挤奶看做一个节点,每次挤奶的间隔天数 $x$ 当做边权,从 $a$ 到 $b$ 连边。

至于 $S_i$,可以看做是第 $0$ 次挤奶后,间隔 $S_i$ 天后,再进行后面的挤奶。

实际建图中我们无需新建 $0$ 号节点,因为我们直接用 $S_i$ 递推就行,相当于已经遍历过这些边。

然后根据题意,不难得到一个节点的 $S_i$ 只能由它的前驱或者节点 $0$ 得到。后者其实就是输入时的 $S_i$。

题目要求的是"最早",实际上要同时满足两个限制(分别为输入时的 $S_i$ 和三元组),我们只能选日期更后的,并为了使其最小,就让它刚好等于那个更后的日期。显然,递推式为:

$S_i = \max{S_i, S_{pre}+v}$

其中 $pre$ 是 $i$ 的前驱节点,$v$ 是连接这两个点的边权。

用拓扑排序求出前驱节点并递推即可。

 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
#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e6, MOD = 80112002;

int n,m,c,in[N],vis[N],ans[N];
vector<pair<int,int>>g[N];

void TopoSort(){
    queue<int>s;
    for(int i=1;i<=n;i++){
        if(!in[i]){
            s.push(i);
        }
    }
    while(!s.empty()){
        int u = s.front();
        s.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(auto [v,x]:g[u]){
            ans[v]=max(ans[v],ans[u]+x);
            if(--in[v]==0){
                s.push(v);
            }
        }
    }
    for(int i=1;i<=n;i++)cout<<ans[i]<<endl;
}

signed main(){
    cin>>n>>m>>c;
    for(int i=1;i<=n;i++)cin>>ans[i];
    for(int i=1;i<=c;i++){
        int u,v,x;
        cin>>u>>v>>x;
        g[u].push_back({v,x});
        in[v]++;
    }
    TopoSort();
    return 0;
}

P1960 郁闷的记者

首先确定,没有输过的队伍一定是第一个输出的,如果这种队伍不只一个,那么就有多种排序方案了。

接着只要正常拓扑即可得到答案。

 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
#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e6, MOD = 80112002;

int n,m,in[N],vis,ans[N];
vector<int>g[N];

void TopoSort(){
    queue<int>s;
    for(int i=1;i<=n;i++){
        if(!in[i]){
            s.push(i);
        }
    }
    if(s.size()>1)vis=1;
    while(!s.empty()){
        int u = s.front();
        cout<<u<<endl;
        int tmp=0;
        s.pop();
        for(auto v:g[u]){
            if(--in[v]==0){
                s.push(v);
                tmp++;
            }
        }
        if(tmp>1)vis=1;
    }
    cout<<vis<<endl;
}

signed main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        in[v]++;
    }
    TopoSort();
    return 0;
}
Licensed under CC BY-NC-SA 4.0
最后更新于 Sep 11, 2025 16:27 +0800
发表了95篇文章 · 总计15万2千字
永远相信美好的事情即将发生。
使用 Hugo 构建
主题 StackJimmy 设计