当前位置: 首页 > news >正文

可达性统计(拓扑排序模板,bitset)

我们还能不能再见面
我向上天苦苦求了几千年
愿意用几世换我们一世情缘
希望可以感动上天

可达性统计(拓扑排序模板,bitset)

目录

  • 可达性统计(拓扑排序模板,bitset)
    • 拓扑排序(Kahn 算法)
    • 代码分析

164. 可达性统计 - AcWing题库

给你一张有向无环图,统计每个点能到达几个点;

一开始想用dfs遍历图查找每个点能通向那些点,但是超时了,所以就就需要用到其他的算法去优化时间了;

紧接着就想到了动态规划,先把每个点能到达的点数存起了,这样上面的点的到达数就是后面的点的到达数的和了!但是现实很骨感,每个点都有可能被多个点到达,多以直接累加的话就会重复计数导致答案错误;

所以我们就需要去重。因此我们也不难得出,每个点能到达的点集就是子节点能到达的点集的并集再加上自己!元就是

f(x)={x}∪(⋃存在有向边(x,y)f(y))f(x)=\{x\}\cup\left(\bigcup_{\text{存在有向边}(x,y)}f(y)\right)f(x)={x}(存在有向边(x,y)f(y))

如果直接用数组去模拟每次求并集的操作每次都要遍历一遍,太慢了。数和数之间的到达情况只分为两种(能到或不能到)所以不难想到利用二进制和位运算去表示,这样每次求并集就是位运算里面的|(按位或)的操作,时间大幅减少!可以利用bitset轻松实现二进制的枚举;

bitset的具体介绍可以看维基,里面很清楚,就是一个二进制的工具;


拓扑排序(Kahn 算法)

分析完题目,思路已经清晰;但是我们应该从那个点入手作为最基础的集合呢?

不难想到,肯定是最后面的点,也就是没有链接其他点的点,这样他的点集就只有他自己,可以作为dp的初始量!

所以这个就可以用到拓扑排序!

给定一张有向无环图,若一个由图中所有点构成的序列 AAA 满足:对于图中的每条边(x,y),x(x,y),x(x,y),xAAA中都出现在yyy之前,则称AAA是该有向无环图顶点的一个拓扑序。求解序列AAA的过程就称为拓扑排序。

拓扑排序过程的思想非常简单,我们只需要不断选择图中入度为 0 的节点 xxx,然后把xxx连向的点的入度减 1。我们可以结合广度优先遍历的框架来高效地实现这个过程:

下面介绍一下Kahn (卡恩) 算法 \text { Kahn (卡恩) 算法 } Kahn (卡恩算法 (利用数组建图时的操作)

p[x]p[x]p[x] 存点 xxx 的邻点。tptptp 存拓扑序列。rd[x]rd[x]rd[x] 存点 xxx 的入度。

算法的核心用队列维护一个入度为 0 的节点的集合。

  1. 初始化。队列 qqq 入压所有入度为 0 的点。
  2. 每次从 qqq 中取出一个点 xxx 放入数组 tptptp
  3. 然后将 xxx 的所有邻点删除。若将边 (x,y)(x, y)(x,y) 删除后,yyy 的入度变为 0,则将 yyy 压入 qqq 中。
  4. 不断重复 2.3 过程,直到队列 qqq 为空。
  5. tptptp 中的元素个数等于 nnn,则有拓扑序;否则,有环。
void topsort(){queue<int> q;for(int i=1;i<=n;i++)if(rd[i]==0)q.push(i);while(q.size()){int t=q.front();q.pop();tp.push_back(t);for(auto i:p[t]){if(--rd[i]==0)q.push(i);}}
}

最后生成的tp数组中里面的点是无环的!所以通过tp数组的长度可以判断原数组中有无环的出现;

而且他的顺序是线性的!也就是如果两点间有通路,那么一定是前面的数指向后面的数!

这样我们就能按照从后往前去遍历拓扑数组,对状态进行累加了;


代码分析

解题思路与策略

该问题是在有向无环图(DAG)上计算每个节点可达的节点数量。核心思路是将拓扑排序与bitset位压缩技术结合,实现高效的可达性统计。

关键步骤

  1. 拓扑排序

    • 初始化队列,将所有入度为0的节点入队
    • 每次取出队首节点加入拓扑序列,并更新其后继节点的入度
    • 当后继节点入度降为0时将其入队
    • 最终得到拓扑排序的序列tp
  2. 逆序DP计算可达集合

    • 关键点:按拓扑序列的逆序处理节点(从后往前)

    • 初始化:当前节点可达自身dp[u][u]=1

    • 状态转移:对于每个节点u,将其所有后继节点的可达集合进行"或"操作:

      dp[u] |= dp[v] v是u的直接后继

    • 逆序处理保证:计算节点u时,其后继节点v都已被计算过

  3. 结果输出

    • 每个节点的dp值是一个bitset,其count()即为可达节点数

    • bitset的大小设为30005(题目数据范围n≤30000)

注意要点

  1. bitset大小设置

    bitset<30005> dp[N] 必须严格大于最大n值,且<>里面不能是变量

  2. 拓扑逆序处理

    for(int i=n-1;i>=0;i--){  // 逆序遍历拓扑序列int t=tp[i];          // 从后往前处理节点
    }
    
  3. 自环处理

    dp[t][t/] = 1 初始化:每个节点可达自身

整体代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define pii pair<int,int>
#define fi first
#define se second
const int N=3e5+5;
int n,m;
vector <int> p[N],tp,rd(N);
bitset<30005> dp[N];
void topsort(){queue<int> q;for(int i=1;i<=n;i++)if(rd[i]==0)q.push(i);while(q.size()){int t=q.front();q.pop();tp.push_back(t);for(auto i:p[t]){if(--rd[i]==0)q.push(i);}}
}
void slove(){cin>>n>>m;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;rd[y]++;p[x].push_back(y);}topsort();for(int i=n-1;i>=0;i--){int t=tp[i];dp[t][t]=1;for(auto j:p[t])dp[t]|=dp[j];}for(int i=1;i<=n;i++)cout<<dp[i].count()<<endl;
} 
signed main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int _=1;//cin>>_;while(_--)slove();return 0;
}

http://www.dtcms.com/a/279128.html

相关文章:

  • 【算法】贪心算法:最大数C++
  • Linux 音频的基石: ALSA
  • 【云服务器安全相关】如何使用 `ping` 命令排查云服务器网络连接问题
  • javascript自执行函数
  • Retinex视网膜算法(SSR、MSR、MSRCR)
  • DNS重绑定
  • 纯净系统清理工具,告别卡顿烦恼
  • iOS高级开发工程师面试——RunTime
  • 构建完整工具链:GCC/G++ + Makefile + Git 自动化开发流程
  • 数字影像产业新机遇 入驻国际数字影像产业园享151项服务
  • 【人工智能】通过 Dify 构建智能助手
  • Day34 Java方法05 可变参数
  • JavaScript中Object.defineProperty的作用和用法以及和proxy的区别
  • 优化菜品呈现:让菜品自己 “开口” 求传播
  • 【ASP.NET Core】内存缓存(MemoryCache)原理、应用及常见问题解析
  • 我的Qt八股文笔记2:Qt并发编程方案对比与QPointer,智能指针方案
  • 电气安全监测系统:筑牢电气安全防线
  • DAOS系统架构-Container
  • 壹[1],异步与多线程
  • 美联储降息趋缓叠加能源需求下调,泰国证券交易所新一代交易系统架构方案——高合规、强韧性、本地化的跨境金融基础设施解决方案
  • 【Linux】Ubuntu22.04安装zabbix
  • 固态金属储氢实用化提速:新氢动力 20 公斤级系统重磅发布
  • GaussDB in的用法
  • Linux部署Mysql
  • JavaScript进阶篇——第一章 作用域与垃圾回收机制
  • Netty编程模型介绍
  • 每天学习一个Python库之os库
  • Debezium日常分享系列之:Debezium 3.2.0.Final发布
  • MySQL Innodb Cluster配置
  • Ubuntu服务器安装Miniconda