图论专题拓扑排序

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 BA+1
根据很明显是一道差分约束问题。
根据差分约束的概念,我们从所有的A向所有的B连一条权值为1的有向边。

然后根据差分约束的套路,我们还要设一个界限才能求出最大值。

因为所有车站级别都是正值,所以 A ≥ 1 A≥1 A1,也就是从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 5005001000=2.5108差分约束的spfa有可能超时

由于本题中的所有点的权值都是大于0,并且一定满足要求 = > => 以上是关于图论专题拓扑排序的主要内容,如果未能解决你的问题,请参考以下文章

图论专题总结

P1983 车站分级(拓扑排序)

NOIP2013pj车站分级[拓扑排序]

基础练习拓扑排序codevs3294 车站分级题解

图论-拓扑排序详解

luogu1983[NOIP2013pjT4] 车站分级(拓扑排序)