洛谷 P3183 [HAOI2016]食物链(记忆化搜索/拓扑排序)

news2024/9/21 8:03:03
[HAOI2016]食物链

给定 n 个物种和 m 条能量流动关系,求其中的食物链条数。物种的名称从 1 到 n 编号, M 条能量流动关系形如 a1​→b1​,a2​→b2​,a3​→b3​⋯am−1​→bm−1​,am​→bm​ 其中 ai​→bi​ 表示能量从物种 ai​ 流向物种 bi​ ,注意单独的一种孤立生物不算一条食物链。

食物链:如果从 x 到 y 的某条路径是沿着能量流动方向移动的,并且 x 没有入边, y 没有出边,则该条路径为一条食物链。

题目保证不会出现重复的能量流动关系,并且能量不会环形流动。

显然,题中所给的的是 DAG ,即有向无环图。我们要做的就是统计这个 DAG 中有多少条不同路径 (u,v) 满足 u 没有入边, v 没有出边。注意相同起点与终点之间可能有多条路径。

我们可以考虑使用动态规划来解决这个问题: 设 f[u] 表示从没有入边的点到达点 u 有多少条不同的路径。

  • 所有没有入边的点的 f 值应该初始化为 1
  • 转移方程为:v​=∑(u,v)∈E​fu​
  • 答案计算为: ans=∑outu​=0且inu​=0​fu​ ,其中 outu​ 为点 u 的出度, inu​ 为点 u 的入度

实现这个动态规划有两种方法:记忆化搜索拓扑排序

我们首先来看如何用记忆化搜索来实现该算法。


记忆化搜索解决 DAG 上 DP

我们观察转移方程: f[v]=∑(u,v)∈E​f[u]

所以在计算一个点时,我们需要递归计算其所有入边,并且将对应点的 f 值累加到该点的 f 值中。所以可以考虑反向建边,这样我们就可以直接通过边表得知一个点应该由哪些点的 f 值来更新。

相应的,由于我们反向建边,最终贡献答案的点需要满足的条件变为:入度为 0 ,并且有出边(如果入度为 0 且没有出边,则该点是一个单独的点)。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 100010; // 点数量
const int maxm = 200010; // 边数量
struct Edge { // 邻接表相关
    int to, next;
} G[maxm];
int head[maxn], cnt, deg[maxn]; // deg 为入度

void addEdge(int u, int v) {
    G[++cnt].to = v;
    G[cnt].next = head[u];
    head[u] = cnt;
    ++deg[v]; // 统计入度
}
int f[maxn];
int dfs(int u){
    if (!head[u]) return 1;
    if (f[u] != -1) return f[u];
    f[u] = 0;
    for(int i = head[u]; i; i = G[i].next){
        int v = G[i].to;
        f[u] += dfs(v);
    }
    return f[u];
}
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    int u, v;
    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        addEdge(v, u); // 反向建边
    }
    int ans = 0;
    memset(f, -1, sizeof(f));
    for(int i = 1; i <= n;i++){
        if (deg[i] == 0 && head[i]) ans += dfs(i);
    }
    printf("%d\n",ans);
    return 0;
}

 拓扑排序解决 DAG 上 DP

观察我们的转移方程:f[v]=∑(u,v)∈E​f[u]

如果有若干条边 u1​→v,u2​→v,⋯uk​→v ,那么只有当 f[u1​],f[u2​],⋯,f[uk​] 全部计算完成时,才可以计算 f[v] 。也即, DAG 中的有向边规定了计算的顺序,只有当一个点的所有前驱点都计算完成时,该点才可以计算。

所以我们需要一个合理的计算顺序,使其满足该图所有的计算顺序要求。

我们可以在这里应用拓扑排序算法来求出拓扑序,然后让方程按照拓扑序进行转移。

与记忆化搜索的把大状态分割为小状态不同,我们使用拓扑序进行转移时采取的方式是将小状态的答案贡献到大状态中。即我们枚举到拓扑序的某一个点 u 时, [u] 已经计算完成,我们需要枚举由该点出发的所有边 (u,v) , 然后将 f[u] 的值贡献到 f[v] 中,即 f[v] += f[u]

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;
const int maxn = 100010; // 点数量
const int maxm = 200010; // 边数量
struct Edge { // 邻接表相关
    int to, next;
} G[maxm];
int head[maxn], cnt, deg[maxn]; // deg 为入度

void addEdge(int u, int v) {
    G[++cnt].to = v;
    G[cnt].next = head[u];
    head[u] = cnt;
    ++deg[v]; // 统计入度
}

int f[maxn], n, m; // f 为dp数组
bool flag[maxn]; // flag 表示在原图中入度是否为 0

int toposort() {
    queue<int> q;
    for(int i = 1; i <= n; i++){
        if (deg[i] == 0){
            flag[i] = true;
            f[i] = 1;
            q.push(i);
        }
    }
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for(int i = head[u]; i ; i = G[i].next){
            int v = G[i].to;
            f[v] += f[u];
            if (--deg[v] == 0) q.push(v); 
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; ++i){
        if(head[i] == 0 && !flag[i])ans += f[i];
    }
    return ans;
}

int main() {
    scanf("%d%d", &n, &m);
    int u, v;
    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        addEdge(u, v);
    }
    printf("%d\n", toposort());
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2094791.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux 驱动】IMX6ULL interrupt驱动

1. GIC驱动初始化 start_kernel (init\main.c) init_IRQ (arch\arm\kernel\irq.c) irqchip_init (drivers\irqchip\irqchip.c) of_irq_init (drivers\of\irq.c) desc->irq_init_cb match->data; ret desc->irq_init_cb(des…

(已开源-CVPR 2024)YOLO-World: Real-Time Open-Vocabulary Object Detection

169期《YOLO-World Real-Time Open-Vocabulary Object Detection》 You Only Look Once (YOLO) 系列检测模型是目前最常用的检测模型之一。然而&#xff0c;它们通常是在预先定义好的目标类别上进行训练&#xff0c;很大程度上限制了它们在开放场景中的可用性。为了解决这一限制…

医学领域实现基于大模型和本地知识库的智能问答系统

在医学领域实现一个基于大模型和本地知识库的智能问答系统&#xff0c;需要考虑医学领域的专业知识和术语。我们将构建一个简单版本的系统&#xff0c;该系统能够处理医学问题&#xff0c;并且能够从本地知识库中检索相关信息来生成答案。 技术栈&#xff1a; 自然语言处理模型…

编译LineageOS模拟器镜像,导出到AndroidStudio

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ 源码下载 LineageOS官网&#xff1a;https://lineageos.org/ LineageOS源码 github 地址&#xff1a;https://github.com/LineageOS/android LineageOS源码国…

讯鹏科技智慧公厕专业供应商,解读智慧公厕有哪些奥秘

在当今科技日新月异的时代&#xff0c;讯鹏科技作为智慧公厕专业供应商&#xff0c;以其先进的技术和创新的解决方案&#xff0c;为人们带来了全新的公共卫生体验。那么&#xff0c;智慧公厕究竟有哪些奥秘呢&#xff1f;让我们一同解读。 一、智慧公厕硬件 1. 环境监测传感器&…

06:【江科大stm32】:定时器输入捕获功能

定时器输入捕获功能 1、通过定时器的输入捕获功能测量PWM波的频率2、PWMI模式测量频率和占空比 1、通过定时器的输入捕获功能测量PWM波的频率 定时器标准库相关的编程接口&#xff1a; ①PWM.c文件的代码如下&#xff1a; /*通过定时器TIM2生成一个分辨率为10us,频率为1KHz的…

八皇后问题代码实现(java,递归)

简介&#xff1a;著名的八皇后问题是由棋手马克斯贝瑟尔在1848年提出来的&#xff0c;要求在 8 8 的棋盘上摆放8个皇后&#xff0c;使”皇后“们不能互相攻击 &#xff0c;当任意两个皇后都不处于同一行、同一列或同一条斜线上时就不会相互攻击&#xff0c;即为目标解。 说明…

C语言中的预处理指令的其中之一——#line

目录 开头1.什么是预处理指令——#line?2.预处理指令——#line的实际应用改__FILE__宏改__LINE__宏改__FILE__宏和__LINE__宏…… 下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。今天&#xff0c;我们要学一下关于C语言中的预处理指令的其中之一——#line的一些…

4-6 使用bios 中断 显示字符

1 显示的逻辑 bios 首先通过中断&#xff0c;访问到 最前面的中断向量表&#xff0c;然后 通过中断向量表然后 访问到具体的 bios 的函数&#xff0c;这些函数是bios 自带的&#xff0c;具体的位置 &#xff0c; 我也不知道。只知道有这个函数。 3 显示的原理 &#xff1b; 主要…

纯蓝图事件

一、创建事件分发器 1、蓝图中可直接添加Event Dispatchers事件分发器 2、还可以设置事件的传递参数 3、直接将创建好的事件分发器拖入EventGraph中会显示出Call、Bind、UnBind、Assign等方法 二、广播事件通知 三、订阅、取消订阅事件通知

算法数学加油站:一元高斯分布(正态分布)Python精美科研绘图(PDF、CDF、PPF、ECDF曲线;QQ图)

这类博客针对算法学习时可能遇到的数学知识补充&#xff0c;但不会太多废话&#xff0c;主要是公式结合Python代码精美绘图理解&#xff01; 本期重点&#xff1a; 参数&#xff1a;期望、标准差曲线&#xff1a;概率密度曲线PDF、累积概率密度函数CDF、百分点函数PPF应用&am…

14:LDO电源模块的布局

1.器件要和边框相聚5mm的距离作为工艺边&#xff0c;工艺边可以布线&#xff0c;但不能摆放器件 LDO布局原则 ①输出靠近负载端 和DCDC布局一样

Springcloud微服务合并打包,重复路径引发的血案

你好&#xff0c;我是柳岸花开。 在微服务架构的世界里&#xff0c;各种服务之间的接口调用犹如人类的神经系统&#xff0c;构成了整个系统的核心。然而&#xff0c;正是这些看似简单的接口路径&#xff0c;可能会引发一场惊天血案。今天&#xff0c;我们就来揭开一起因“重复路…

Git高手必备:掌握这些指令,轻松玩转版本控制(一)

前言 注&#xff1a;本文下的除非特殊声明&#xff0c;否则一律不作为实际加号&#xff0c;仅表示连接 所有的版本控制系统&#xff0c;只能跟踪文本文件的改动比如txt文件&#xff0c;网页&#xff0c;所有程序的代码等&#xff0c;能清楚的知道改动了什么。但是类似于图片、…

嵌入式全栈开发学习笔记---Linux系统编程(文件编程)

目录 Linux文件概述 系统IO 创建文件creat() 打开文件open() 写文件write() 读文件read() 文件指针---lseek() 系统IO拷贝 标准IO 标准IO和系统IO的区别 缓冲区的分类 行缓存测试 打开文件fopen() 写文件fwrite() 读文件read() 标准IO拷贝 标准IO和系统IO的效…

实践:根据时区显示时间

背景 在数据库中存储时间&#xff0c;不会自动对时区进行处理&#xff0c;要想针对不同时区作时间显示的适配&#xff0c;需要在程序中做适配&#xff0c;本文即为解决这一问题的实践案例。 数据库存 UTC 时间 插入记录时&#xff0c;使用 datetime.utcnow()获取当前 utc 时…

MFCC C++实现与Python库可视化对比

MFCC C实现与Python库对比 MFCC理论基础 在音频、语音信号处理领域&#xff0c;我们需要将信号转换成对应的语谱图(spectrogram)&#xff0c;将语谱图上的数据作为信号的特征。语谱图的横轴x为时间&#xff0c;纵轴y为频率&#xff0c;(x,y)对应的数值代表在时间x时频率y的幅…

动作损失 ​ 的定义

动作损失 La是在弱监督时间动作定位&#xff08;Weakly-Supervised Temporal Action Localization, WSTAL&#xff09;任务中用于优化模型的一种损失函数。它的主要目标是确保模型能够准确地预测视频中动作发生的时间段&#xff0c;并对视频级别标签进行良好的分类。下面是对动…

【Python系列】 Python 中的枚举使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

PyTorch中,动态调整学习率(Learning Rate Scheduling),也可以根据损失函数的损失数值自动调整学习率

在PyTorch中&#xff0c;动态调整学习率&#xff08;Learning Rate Scheduling&#xff09;是一种常用的技术&#xff0c; 用于在训练过程中根据一定的策略调整学习率&#xff0c;以优化模型的训练效果和收敛速度。以下是一些常见的学习率调整策略&#xff1a; 1. **固定步长…