图论专题拓扑排序
Posted 繁凡さん
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论专题拓扑排序相关的知识,希望对你有一定的参考价值。
拓扑排序
给定一张有向无环图,若一个序列A满足图中的任意一条边(x,y)x都在y的前面呢么序列A就是图的拓扑排序
实际上拓扑排序就是满足所有的边x指向y,x一定在y的前面。这样按照拓扑排序递推,就可以满足每一个状态都没有循环依赖 − > -> −> 没有后效性 − > -> −>可以满足DP递推
题目 | 算法 |
---|---|
AcWing 1191. 家谱树 | toposort模板题 |
AcWing 1192. 奖金 | 差分约束+拓扑序 = 递推求最长路 |
AcWing 164. 可达性统计 | 拓扑图递推求答案 |
AcWing 456. 车站分级 | 拓扑图递推求解差分约束系统 |
A、AcWing 1191. 家谱树(toposort模板题)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110, M = 50007;
int ver[M], nex[M], edge[M], head[N], tot;
int n, m;
int d[N];//入度
bool vis[N];
int q[M];
//int ans[M];
int cnt;
void add(int x,int y)
ver[tot] = y;
nex[tot] = head[x];
head[x] = tot ++ ;
d[y] ++ ;
void toposort()
int hh = 0, tt = -1;
for(int i = 1;i <= n;++i)
if(!d[i])
q[++ tt] = i;
if(tt == M)tt = 0;
while(hh <= tt)
int x = q[hh ++ ];
//ans[++ cnt] = x;
if(hh == M )hh = 0;
for(int i = head[x];~i;i = nex[i])
int y = ver[i];
if(-- d[y] == 0)
q[ ++ tt] = y;
int main()
scanf("%d",&n);
memset(head, -1, sizeof head);
for(int i = 1;i <= n;++i)
int son;
while(~scanf("%d",&son) && son)
add(i, son);
toposort();
for(int i = 0;i < n;++i)
printf("%d ",q[i]);
B、AcWing 1192. 奖金(差分约束+拓扑序 = 递推求最长路 )
实际上是一道差分约束的题,要求最小值,也就是取所有下界的最大值,用最长路
如果用差分约束spfa的时间复杂度为 O ( k m ) O(km) O(km) ~ O ( n m ) O(nm) O(nm),可能会被卡
因为本题中所有的权值都是正值,因此我们可以不用差分约束就求这个最长路。
我们直接拓扑排序得到一张有向无环图DAG
=
>
=>
=> 每一个状态都没有循环依赖
=
>
=>
=> 没有后效性
=
>
=>
=> 就可以用DP递推求最长路。
然后别忘了条件 a >= b + 1 等价于b向a连一条权值为1的边。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 50007, M = 500007;
int n, m;
int dist[N];//longest way
int ver[M], edge[M], nex[M], head[N], tot;
int q[M];
int din[N];
void add(int x,int y, int z)
ver[tot] = y;
nex[tot] = head[x];
edge[tot] = z;
head[x] = tot ++ ;
din[y] ++ ;
bool toposort()
int hh = 0, tt = -1;
for(int i = 1;i <= n;++i)
if(!din[i])
q[ ++ tt] = i;
if(tt == M)tt = 0;//round-robin queue
while(hh <= tt)
int x = q[hh ++ ];
if(hh == M)hh = 0;
for(int i = head[x];~i;i = nex[i])
int y = ver[i];
if(-- din[y] == 0)
q[++ tt] = y;
return tt == n - 1;//because the initial tt which had been used is 0;
int main()
scanf("%d%d",&n,&m);
memset(head,-1,sizeof head);
for(int i = 1; i <= m;++i)
int x,y;
scanf("%d%d", &x, &y);
add(y,x,1);//a >= b + 1 -> (b,a,1)
if(!toposort())puts("Poor Xed");
else
for(int i = 1;i <= n;++i)dist[i] = 100;
for(int i = 0;i < n;++i)//run the topological sequence
//be attention to the array q is beginning in 0
int x = q[i];
for(int j = head[x];~j;j = nex[j])
int y = ver[j], z = edge[j];
dist[y] = max(dist[y], dist[x] + z);
int res = 0;
for(int i = 1;i <= n;++i)
res += dist[i];
printf("%d\\n",res);
return 0;
C、AcWing 164. 可达性统计(拓扑图递推求答案)
有向无环图 = > => => 拓扑排序 = > => => 拓扑图 = > => =>每一个状态都没有循环依赖 = > => =>没有后效性 = > => =>可以DP / 递推求解答案(比如最短 / 长路)
我们用
f
[
i
]
f[i]
f[i]表示i可以到达多少个点.
例如:
i
i
i 可以到达的点为
j
1
,
j
2
j_1,j_2
j1,j2 ,则
f
[
i
]
=
f
[
i
]
∪
f
[
j
1
]
∪
f
[
j
2
]
f[i] =f[i] ∪ f[j_1]∪f[j_2]
f[i]=f[i]∪f[j1]∪f[j2]
算
f
[
i
]
f[i]
f[i] 的时候把所有的
f
[
i
]
f[i]
f[i]都算出来
=
>
=>
=>用拓扑排序的逆序推一遍即可
为了防止计算重复,我们可以使用状态压缩,用一个长度为n的二进制字符串代表它的状态。
但是数据较大,会超时
我们可以用STL中的
b
i
t
s
e
t
bitset
bitset存每一个点可以到达的点用f[i].count()
计算有多少个1也就是有多少个可到达的点。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<bitset>
using namespace std;
const int N = 50007, M = 500007;
int n, m;
int ver[M], nex[M], head[M], tot;
int din[N];
int q[N];
bitset<N>f[N];//它本身就是一个容器,这里开N个bitset容器
void add(int x,int y)
ver[tot] = y;
nex[tot] = head[x];
head[x] = tot ++ ;
din[y] ++ ;
void toposort()
int hh = 0, tt = -1;
for(int i = 1;i <= n;++i)
if(!din[i])
q[++ tt ] = i;
if(tt == N)tt = 0;
while(hh <= tt)
int x = q[hh ++ ];
if(hh == N)hh = 0;
for(int i = head[x];~i;i = nex[i])
int y = ver[i];
if(-- din[y] == 0)
q[++ tt] = y;
if(tt == N)tt = 0;
void calc()
for(int i = n - 1;i >= 0;-- i)
int x = q[i];
f[x][x] = 1;//the initialization of DP,表示自己能到达自己
for(int j = head[x];~j;j = nex[j])
int y = ver[j];
f[x] |= f[y];//求并集
int main()
scanf("%d%d", &n, &m);
memset(head,-1,sizeof head);
for(int i = 1;i <= m;++i)
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
toposort();
calc();
for(int i = 1;i <= n;++i)
printf("%d\\n",f[i].count());//一共就多少个1就代表能到达多少个点
return 0;
D、AcWing 456. 车站分级
差分约束裸题。
计算当前线路中最小的级别(比较始发站和终点站)。
整条线路中所有大于这个级别的都必须停靠
所有未停靠的站点的级别一定小于这个级别
也就是说所有未停靠的即为级别低,记为A
所有停靠的站点级别一定比A的高,记作B
得到公式
B
≥
A
+
1
B ≥ A + 1
B≥A+1
根据很明显是一道差分约束问题。
根据差分约束的概念,我们从所有的A向所有的B连一条权值为1的有向边。
然后根据差分约束的套路,我们还要设一个界限才能求出最大值。
因为所有车站级别都是正值,所以
A
≥
1
A≥1
A≥1,也就是从0向所有的A中的点连一条权值为1 的有向边。我们常常用直接给dist
数组赋值为1代替。
但是由于实际数据范围较大
最坏情况下是有1000
趟火车,每趟有1000
个点,每趟上限有500
个点停站,则有(1000 - 500)
个点不停站,不停站的点都向停站的点连有向边,则总共有
500
∗
500
∗
1000
=
2.5
∗
1
0
8
500 * 500 * 1000 = 2.5 * 10^8
500∗500∗1000=2.5∗108,差分约束的spfa
有可能超时。
由于本题中的所有点的权值都是大于0,并且一定满足要求
=
>
=>
以上是关于图论专题拓扑排序的主要内容,如果未能解决你的问题,请参考以下文章