P4017 最大食物链计数 (拓扑排序)

本文讲解了如何通过拓扑排序解决食物链问题,利用节点的入度和出度寻找最佳生产者到最佳消费者的最长路径,关键步骤包括标记最佳生产者、转移权重并更新答案。最终通过代码实例展示了如何利用队列实现算法并正确处理入度和出度条件。
摘要由CSDN通过智能技术生成

首先 ,要知道这道题是 TopoTopo 拓扑排序。不妨先从拓扑排序定义下手,分析题目的性质。经分析得:

食物链中的生物 —— 节点

生物之间的关系 —— 有向边

为了方便描述,我们将

不会捕食其他生物的 生产者 叫做 最佳生产者

不会被其他生物捕食的 消费者 叫做 最佳消费者

由于数据中不会出现环,所以 最大食物链 即 左端是 最佳生产者 ,右端是 最佳消费者 的路径

只要最左端是 最佳生产者 的路径(即最右端可以不是 最佳消费者 的最大食物链) 我们称之为 类食物链

既然 食物链中的生物 可以看成 节点,那么 最佳生产者 的入度一定为 0, 而 最佳消费者 的出度也为 0。

想要找到一条 最大食物链 ,那么这条路径的 起点 入度要为0,终点 出度要为0。

故:既要记录入度,还要记录出度!

现在的问题转换成了,如何找到图中所有 左端点入度为0 且 右端点出度为0 的路径的数量

我们拿起笔,在草稿纸上画一个图进行推算。接下来将使用 样例 进行举例。

 (将 最佳生产者 涂上 蓝色,最佳消费者 涂上 红色)

发现: 答案为 到所有 红色点 的路径条数的 总和 

(这里的 路径条数总和 不是 连向它有几条边 ,而是以它结束的 最大食物链 数量的总和)

对于上图,55 号点的对应路径数量 取决于:以 到 55 号点的三个点( 22 号、33 号、44 号) 结尾的 类食物链 条数的总和。

而 以 22 号、33 号、44 号 结尾的 类食物链 取决于:以 可以到达 22 号、33号、44号点 的点 结尾的 类食物链 条数的总和。

以此类推,显然对于 以 任一点 结尾的 类食物链 的数量,都取决于 蓝色点

各点数量对应关系在下图用绿色边标注

重点:

使用拓扑排序,由题意得知 Topo 排序第一轮被删掉的点 一定是 蓝色点(最佳生产者),而令蓝色点 的答案为 1。

当第一轮删点时,将蓝色点可以到的点 的答案 都加上 蓝色点的 答案(即加 1)。

即:拓扑排序 需要删除的点的答案 都累加到 它可以到达的点 上面去这样我们就将边的累加转换到了 点之间的累加。 最后累加所有红色点(最佳消费者) 的答案,输出即可。

以下是模拟操作过程:

第一轮:删除 1 号蓝色点,1 号蓝色点可以到的点(2号点、3 号点)都加 1

第二轮:删除 2 号点,2 号点可以到的点(3 号点、5 号红色点)都加 1。此时 3 号点答案为 2,5 号点答案为 1

第三轮:删除 3 号点,3 号点可以到的点(4 号点、5 号红色点)都加 2。此时 5 号点答案为 3,4 号点答案为 2

第四轮:最后删除 4 号点,4 号点可以到的点(5 号红色点)加 2,此时 5 号点答案为5

可见全图只有 5 号一个红色点,那么答案就是 5 号点的答案———— 5 了

那么代码实现就很简单了!

AC代码

#include<bits/stdc++.h>
using namespace std;
queue<int>q;//开队列
int s[5050][5050];//记录被吃关系  s[i][j] i-->j
int in[5050];//入度
int out[5050];//出度
int d[5050];//传值
int ans;//记录答案
#define mod 80112002 //模
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1; i<=m; i++)
    {
        int a,b;
        cin>>a>>b;
        s[a][b]=1;
        out[a]++;
        in[b]++;
    }
    for(int i=1; i<=n; i++)
    {
        if(in[i]==0)//记录入度为0也就是生产者
        {
            d[i]=1;//赋初值
            q.push(i);//加入队列
        }
    }
    while(!q.empty())//队列空时结束
    {
        int a=q.front();//取出队列中第一个数
        q.pop();//删除第一个数
        for(int i=1; i<=n; i++)
        {
            if(s[a][i]==0)
                continue;
            d[i]+=d[a];//赋值转移
            d[i]%=mod;
            in[i]--;//前一个数被删除入度减一
            if(in[i]==0)//入度为0时加入队列
            {
                q.push(i);
                if(out[i]==0)//出度为0说明这是最高级捕食者
                {
                    ans+=d[i];//更新答案
                    ans%=mod;//不要忘了取模
                }
            }
        }
    }
    cout<<ans;
    return 0;
}

 值得注意的是

if(in[i]==0)
{
    q.push(i);
    if(out[i]==0)
    {
        ans+=d[i];
        ans%=mod;
    }
}

这一部分代码我最开始是这样写的

if(in[i]==0)
    q.push(i);
    
if(out[i]==0)
{
    ans+=d[i];
    ans%=mod;
}

 将两个判断分开,导致答案变大,原因在于只能在入度和出度都为0时更新答案,如果不判断入度

就会导致多次更新答案导致答案变大。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值