可达性统计(拓扑排序模板,bitset)
我们还能不能再见面
我向上天苦苦求了几千年
愿意用几世换我们一世情缘
希望可以感动上天
可达性统计(拓扑排序模板,bitset)
给你一张有向无环图,统计每个点能到达几个点;
一开始想用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的初始量!
所以这个就可以用到拓扑排序!
给定一张有向无环图,若一个由图中所有点构成的序列 A A A 满足:对于图中的每条边 ( x , y ) , x (x,y),x (x,y),x在 A A A中都出现在 y y y之前,则称 A A A是该有向无环图顶点的一个拓扑序。求解序列 A A A的过程就称为拓扑排序。
拓扑排序过程的思想非常简单,我们只需要不断选择图中入度为 0 的节点 x x x,然后把 x x x连向的点的入度减 1。我们可以结合广度优先遍历的框架来高效地实现这个过程:
下面介绍一下 Kahn (卡恩) 算法 \text { Kahn (卡恩) 算法 } Kahn (卡恩) 算法 (利用数组建图时的操作)
p [ x ] p[x] p[x] 存点 x x x 的邻点。 t p tp tp 存拓扑序列。 r d [ x ] rd[x] rd[x] 存点 x x x 的入度。
算法的核心用队列维护一个入度为 0 的节点的集合。
- 初始化。队列 q q q 入压所有入度为 0 的点。
- 每次从 q q q 中取出一个点 x x x 放入数组 t p tp tp。
- 然后将 x x x 的所有邻点删除。若将边 ( x , y ) (x, y) (x,y) 删除后, y y y 的入度变为 0,则将 y y y 压入 q q q 中。
- 不断重复 2.3 过程,直到队列 q q q 为空。
- 若 t p tp tp 中的元素个数等于 n n 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);
}
}
}
最后生成的tp数组中里面的点是无环的!所以通过tp数组的长度可以判断原数组中有无环的出现;
而且他的顺序是线性的!也就是如果两点间有通路,那么一定是前面的数指向后面的数!
这样我们就能按照从后往前去遍历拓扑数组,对状态进行累加了;
代码分析
解题思路与策略
该问题是在有向无环图(DAG)上计算每个节点可达的节点数量。核心思路是将拓扑排序与bitset位压缩技术结合,实现高效的可达性统计。
关键步骤
-
拓扑排序:
- 初始化队列,将所有入度为0的节点入队
- 每次取出队首节点加入拓扑序列,并更新其后继节点的入度
- 当后继节点入度降为0时将其入队
- 最终得到拓扑排序的序列tp
-
逆序DP计算可达集合:
-
关键点:按拓扑序列的逆序处理节点(从后往前)
-
初始化:当前节点可达自身
dp[u][u]=1 -
状态转移:对于每个节点u,将其所有后继节点的可达集合进行"或"操作:
dp[u] |= dp[v]v是u的直接后继 -
逆序处理保证:计算节点u时,其后继节点v都已被计算过
-
-
结果输出:
-
每个节点的dp值是一个bitset,其count()即为可达节点数
-
bitset的大小设为30005(题目数据范围n≤30000)
-
注意要点
-
bitset大小设置:
bitset<30005> dp[N]必须严格大于最大n值,且<>里面不能是变量 -
拓扑逆序处理:
for(int i=n-1;i>=0;i--){ // 逆序遍历拓扑序列 int t=tp[i]; // 从后往前处理节点 } -
自环处理:
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;
}
更多推荐



所有评论(0)