图论:拓扑排序

问题的引入

首先引入以下问题:P1113 杂务

对于该问题,事件n的完成时间为max{事件i的完成时间+事件n的耗时},其中事件i是事件n的准备工作,由此得到dp方程。发现在计算n时,必须先得到事件i的完成时间才行。对于本题,由于限定了事件n的准备工作一定小于n,因此直接从1到n进行dp转移并不会出现问题。代码如下:

#include<bits/stdc++.h>
using namespace std;
struct edge{
    int value,time;
    vector<int> from;
}graph[100010];
bool gone[100010];
int dfs(int x){
    if(gone[x]) return graph[x].time;
    for(int i=0;i<graph[x].from.size();i++){
        graph[x].time=max(graph[x].time,graph[graph[x].from[i]].time+graph[x].value);
    }
    gone[x]=1;
    return graph[x].time;
}
int main(){
    int n,ans=0;
    cin>>n;
    for(int i=1;i<=n;i++){
        int id,len,father;
        cin>>id>>len;
        graph[id].value=len;
        graph[id].time=len;
        while(cin>>father&&father!=0){
            graph[id].from.push_back(father);
        }
    }
    for(int i=1;i<=n;i++) ans=max(ans,dfs(i));
    cout<<ans;
    return 0;
}

然而,如果题目中没有限定能到达点n的点的序号范围,则必须要确定一个合适的计算顺序。

例如该问题:P4017 最大食物链计数

拓扑排序

拓扑排序是在DAG(有向无环图)上对点进行排序,使得在搜到点n时所有能到达点n的点都已经被搜过了。

具体流程如下:

  1. 将所有入度为0的点加入处理队列

  1. 将处于队头的点x取出,遍历x所能到达的所有点y

  1. 对于每个y,删去从点x到点y的边。在具体的实现中,让y的入度减一

  1. 如果y的入度为0,说明所有能到y的点都被计算过了,这时将y加入处理队列

  1. 重复步骤ii直到处理队列为空

  1. 处理的顺序即是搜索的顺序

应用

对于本题,可以在拓扑排序的过程中对每个点的状态进行维护。代码如下:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 5010
#define MOD 80112002
int m,n;
struct Node{
    int value=0,in_degree=0,out_degree=0;
    vector<int> from,to;
}g[5010];
void solve(){
    queue<int> q,q_ans;
    for(int i=1;i<=n;i++){
        if(g[i].in_degree==0){
            q.push(i);
            g[i].value=1;
        }
        if(g[i].out_degree==0){
            q_ans.push(i);
        }
    }
    while(!q.empty()){
        int x_n=q.front();
        q.pop();
        for(int i=0;i<g[x_n].out_degree;i++){
            int x0=g[x_n].to[i];
            g[x0].in_degree--;
            g[x0].value+=g[x_n].value;
            g[x0].value%=MOD;
            if(g[x0].in_degree==0) q.push(x0);
        }
    }
    int ans=0;
    while(!q_ans.empty()){
        ans=(ans+g[q_ans.front()].value)%MOD;
        q_ans.pop();
    }
    printf("%d",ans);
}
int main(){
    int x,y;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        scanf("%d%d",&x,&y);//x被吃,y吃
        g[x].to.push_back(y);
        g[y].from.push_back(x);
        g[y].in_degree++;
        g[x].out_degree++;
    }
    solve();
    return 0;
}

相关练习

P1807 最长路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值