洛谷P4017 最大食物链计数

题目信息

题目要求

样例输入/输出 

算法简介 

要知道题目需要用到什么样的算法,首先得捋清楚题目的意思

比如这个题目,我们读题后可以获得这样的信息:

(1)节点之间构成有向边

(2)所有边不会构成环

(3)需要求的所有的边没有边权而且一定是从入度为零的节点到出度为零的节点

基于以上三点我们基本可以确定可以尝试用拓扑排序解决问题。

拓扑排序的定义如下:

 

算法过程

拓扑排序的过程十分简单,借助bfs完成排序,首先把所有入度为零的节点入队,然后把这些点能到的节点的度数都减去一,再将这个节点出队,如果有节点减到0了那就证明这个节点可以入队作为下一轮节点筛选的基准。这样的过程一直到队列为空。

代码模板

vector<int> topo_sort(const int &k, vector<vector<int>>& edges) {
        vector<vector<int>> g(k);
        vector<int> order, left(k, 0);
        for (auto& edge : edges) {
            int x = edge[0] - 1, y = edge[1] - 1;
            g[x].push_back(y);
            ++left[y];
        }
        queue<int> q;
        for (int i = 0; i < k; i++) {
            if (left[i] == 0) q.push(i);
        }
        while (!q.empty()) {
            int node = q.front(); q.pop();
            order.push_back(node + 1);
            for (auto& x : g[node]) {
                --left[x];
                if (left[x] == 0) q.push(x);
            }
        }
        return order.size() == k ? order : vector<int> ();
    }

代码细节

拓扑排序一般使用邻接矩阵存图,用vector将每个节点可以到达的点都放在相应的数组中,再放的同时将可以到达的节点的入度加上一,这样迭代完所有的节点之后整张图就存下来了。

另外在实现排序的时候,利用了队列的扩展性,将所有的节点分层入队,再将每一层的节点能到达的所有节点全部遍历一遍,并将其入度减一,如果发现某节点入度变成0了,就将这个节点入队作为下一层的基准。迭代完成后将这个节点弹出队列即可。这样一直到队列为空时,整个拓扑排序的算法完成。注意如果需要得到排序列表的话,在每个节点出队列前将其压入目标列表ordoer即可。

解题思路

首先我们可以将每次输入看成条从相对消费者到相对生产者的一条有向无权边,这样可以实现边输入边建图。

代码如下:

for (int i = 0, u, v; i < m; i++) {
        cin >> u >> v;
        graph[v].push_back(u);
        ++left[u];
    }

建完图之后,我们需要做的就就是进行拓扑排序,这个过程并不复杂,其实就是一个简单的bfs。

不过值得一提的是这个题是要求所有从入度为零的点到所有出度为零的点的所有边的总和

这样的话我们需要一个额外的数组cnt,这个数组在进行拓扑排序的同时不断更新,即从入度为零的点开始,并且将入度为零的点初始化cnt的值为1,每次都把其结果加到能到达的点上面,这样最后再遍历一遍所有出度为零的点,将它们所有的cnt求个和即可

代码细节如下

    queue<int> q;
    for (int i = 1; i <= n; i++) {
        if (left[i] == 0) {
            q.push(i);
            cnt[i] = 1;
        }
    }
    while (!q.empty()) {
        int node = q.front(); q.pop();
        for (auto& x : graph[node]) {
            --left[x];
            cnt[x] += cnt[node]; 
            cnt[x] %= MOD;
            if (left[x] == 0) q.push(x);
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (graph[i].size() == 0) {
            ans += cnt[i];
            ans %= MOD;
        }
    }

全部代码

#include<iostream>
#include<queue>
#include<stack>
#include<string>
#include<map>
#include<set>
#include<unordered_map>
#include<algorithm>
using namespace std;

int init = [] {
    cin.tie(nullptr)->sync_with_stdio(false);
    return 0;
}();

const int MOD = 80112002;
typedef pair<int, int> PII;

int main(){
    int n, m, ans = 0;
    cin >> n >> m;
    vector<vector<int>> graph(n + 1);
    vector<int> left(n + 1, 0);
    vector<int> cnt(n + 1, 0);
    for (int i = 0, u, v; i < m; i++) {
        cin >> u >> v;
        graph[v].push_back(u);
        ++left[u];
    }
    queue<int> q;
    for (int i = 1; i <= n; i++) {
        if (left[i] == 0) {
            q.push(i);
            cnt[i] = 1;
        }
    }
    while (!q.empty()) {
        int node = q.front(); q.pop();
        for (auto& x : graph[node]) {
            --left[x];
            cnt[x] += cnt[node]; 
            cnt[x] %= MOD;
            if (left[x] == 0) q.push(x);
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (graph[i].size() == 0) {
            ans += cnt[i];
            ans %= MOD;
        }
    }
    cout << ans << endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值