拓扑排序复习——Chemist

一、基本算法

拓扑序列:对于一张有向图,求一个序列ai若对于每一条边(u,v),都满足au<=a,则称这个序列为这张有向图的拓扑序列,一张图可能有多个拓扑序列。

求拓扑序列:找到入度为0的点,加入队列中,每次取出队列顶端的点加入拓扑序列的最后,将它到达的点的入度-1,然后再重复做,直到没有点的入度为0,若最后还是有点的入度大于0,则说明有向图中存在环。

代码:

void add(int x,int y)
{
    num++;
    in[y]++;
    End[num]=y;
    Next[num]=Head[x];
    Head[x]=num;
}
void topsort()
{
    queue<int>q;
    for(int i=1;i<=n;i++)
     if(!in[i])q.push(i);
    while(!q.empty())
    {
        int x=q.front();q.pop();
        seq[++cnt]=x;
        for(int i=Head[x];i;i=Next[i])
        {
            int y=End[i];
            in[y]--;
            if(in[y]==0)q.push(y);
        }
    }
}

二、应用

1.求字典序最小/最大的拓扑序列。

只需将上面代码中的队列换成小根堆/大根堆即可,每次取出堆顶,方法相同。

2.洛谷P1983车站分级

由于存在明显的优先级关系,所以考虑拓扑排序,按照小于的关系连边,然后拓扑排序一遍,找到最大的d[i]就是最少需要分的级别,因为这一段序列中的点必须满足级别严格递减。(再具体的题解可以看cellur的题解)

代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int M=2e6;
 4 int n,m,cnt=0,sta[2018],d[2018],in[2018],w[2018];
 5 bool v[2018];
 6 int num=0,end[M],next[M],head[2018];
 7 void add(int x,int y)
 8 {
 9     if(0)puts("AC");
10     num++;
11     in[y]++;
12     end[num]=y;
13     next[num]=head[x];
14     head[x]=num;
15 }
16 void topsort()
17 {
18     queue<int>q;
19     for(int i=1;i<=n+m;i++)
20      if(!in[i])q.push(i),d[i]=w[i];
21     while(q.size())
22     {
23         int x=q.front();q.pop();
24         for(int i=head[x];i;i=next[i])
25         {
26             int y=end[i];
27             in[y]--;
28             d[y]=d[x]+w[y];
29             if(in[y]==0)q.push(y);
30         }
31     }
32 }
33 int main()
34 {
35     cin>>n>>m;
36     for(int i=1;i<=n;i++)
37      w[i]=1;
38     for(int i=1;i<=m;i++)
39     {
40         memset(v,0,sizeof v);
41         cin>>cnt;
42         for(int j=1;j<=cnt;j++)
43          scanf("%d",&sta[j]),v[sta[j]]=1;
44         for(int j=sta[1];j<=sta[cnt];j++)
45          if(!v[j])add(j,n+i);
46         for(int j=1;j<=cnt;j++)
47          add(n+i,sta[j]);
48     }
49     topsort();
50     int ans=0;
51     for(int i=1;i<=n;i++)
52      ans=max(ans,d[i]);
53     cout<<ans<<endl;
54     return 0;
55 }

3.洛谷P3243菜肴

最先想到的就是求出字典序最小的拓扑序列,然而我们很容易举出反例,如两个限制:5>2,4>3,这时的答案显然是1 5 2 4 3,而不是1 4 3 5 2,而后者的字典序小于前者。那么我们不妨换一种思路,在反图中求出字典序最大的序列,然后反着输出,如果遇到环就特判输出Impossible。这样为什么是对的呢?因为求最大的拓扑序的话就是在能放的中选择一个最大的放到最前面,如果不放这个而放次大的能放的,那么反过来之后,这个最大的一定在次大的前面,而本来它是可以放到次大的后面的,所以这样一定是最优的。

至于求字典序最大的拓扑序的方法在前面已经说过,这里用大根堆实现,直接看代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int M=1e5+10;
 4 int T,n,m,in[M],seq[M];
 5 int num=0,cnt=0,Head[M],Next[M],End[M];
 6 void add(int x,int y)
 7 {
 8     num++;
 9     End[num]=y;
10     Next[num]=Head[x];
11     Head[x]=num;
12 }
13 void clear()
14 {
15     cnt=num=0;
16     for(int i=1;i<=n;i++)
17      in[i]=Head[i]=0;
18     for(int i=1;i<=m;i++)
19      Next[i]=End[i]=0;
20 }
21 bool topsort()
22 {
23     priority_queue<int>q;//大根堆
24     for(int i=n;i>=1;i--)
25      if(!in[i])q.push(i);
26     while(!q.empty())
27     {
28         int x=q.top();q.pop();
29         seq[++cnt]=x;
30         for(int i=Head[x];i;i=Next[i])
31         {
32             int y=End[i];
33             in[y]--;
34             if(in[y]==0)q.push(y);
35         }
36     }
37     for(int i=1;i<=n;i++)
38      if(in[i]>0)return 0;
39     return 1;
40 }
41 int main()
42 {
43     scanf("%d",&T);
44     while(T--)
45     {
46         scanf("%d%d",&n,&m);
47         clear();
48         for(int i=1;i<=m;i++)
49         {
50             int x,y;
51             scanf("%d%d",&x,&y);
52             add(y,x);
53             in[x]++;
54         }
55         if(!topsort())puts("Impossible!");
56         else{
57             for(int i=n;i>=1;i--)
58              printf("%d ",seq[i]);
59             puts("");
60         }
61     }
62     return 0;
63 }

4.HDU5222 Exploration

题意:给你一张混合图(既有有向边又有无向边),问有没有简单环。

先将无向边连接的点通过并查集合并成一个点,同时判断是否只通过无向边就能形成环,然后再加入有向边,注意添加的边的起始点为起始点所在集合编号,终点为终点所在集合编号,同时也要判断添加的起始点是否已经属于同一个集合,然后拓扑排序一遍,判断缩点后的有向图是否存在环,当然也可以用Tarjan做。注意多组数据一定要每次把邻接表也清零,因为这个WA了好多次。。。

代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<queue>
 4 using namespace std;
 5 const int M=1e6+10;
 6 int T,n,m1,m2;
 7 int fa[M],in[M];
 8 int num=0,Head[M],Next[M],End[M];
 9 int get(int x)
10 {
11     if(x==fa[x])return x;
12     else return fa[x]=get(fa[x]);
13 }
14 void Union(int x,int y)
15 {
16     fa[get(x)]=get(y);
17 }
18 void add(int x,int y)
19 {
20     num++;
21     End[num]=y;
22     Next[num]=Head[x];
23     Head[x]=num;
24 }
25 void clear()
26 {
27     num=0;
28     for(int i=1;i<=n;i++)
29      fa[i]=i,in[i]=0,Head[i]=0;
30     for(int i=1;i<=m2;i++)
31      End[i]=Next[i]=0;
32 }
33 bool topsort()
34 {
35     queue<int>q;
36     for(int i=1;i<=n;i++)
37      if(fa[i]==i&&in[i]==0)q.push(i);
38     while(q.size())
39     {
40         int x=q.front();q.pop();
41         for(int i=Head[x];i;i=Next[i])
42         {
43             int y=End[i];
44             in[y]--;
45             if(in[y]==0)q.push(y);
46         }
47     }
48     for(int i=1;i<=n;i++)
49      if(fa[i]==i&&in[i]>0)return 1;
50     return 0;
51 }
52 int main()
53 {
54     scanf("%d",&T);
55     while(T--)
56     {
57         bool flag=0;
58         int x,y;
59         scanf("%d%d%d",&n,&m1,&m2);
60         clear();
61         for(int i=1;i<=m1;i++)//加无向边
62         {
63             scanf("%d%d",&x,&y);
64             if(get(x)==get(y))flag=1;
65             //如果无向边就能构成环
66             else Union(x,y);
67         }
68         for(int i=1;i<=m2;i++)
69         {
70             scanf("%d%d",&x,&y);
71             int fx=get(x),fy=get(y);
72             if(fx==fy)flag=1;
73             add(fx,fy);
74             in[fy]++;
75         }
76         if(flag)puts("YES");
77         else{
78             bool f=topsort();
79             if(f)puts("YES");
80             else puts("NO");
81         }
82     }
83     return 0;
84 }

原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9670285.html

时间: 2024-11-05 20:44:00

拓扑排序复习——Chemist的相关文章

【模拟题(63550802...)】解题报告【贪心】【拓扑排序】【找规律】【树相关】

目录: 1.A[树相关]    2.B[找规律]    3.C[贪心][拓扑排序] A. 描述(A 输入文件 : A.input 输出文件 : A.output)一个城市的构成是一颗n 个节点的树(2 ≤ n ≤ 200), 现在需要在树中找出两条不相交的路径(即两条路径不能有重边也不能有重点),使得路径的长度的乘积最大.输入描述第一行一个数n 表示这个城市一共有 n 个节点.接下来 n-1 行,每行两个数ai 和bi (1 ≤ ai,bi ≤ n ),分别表示从ai 到bi,有一条边,每条边的

【每日算法】图算法(遍历&amp;MST&amp;最短路径&amp;拓扑排序)

图有邻接矩阵和邻接表两种存储方法,邻接矩阵很简单,这里不讨论,下面我们先看看常用的邻接表表示方法. 邻接表常用表示方法 指针表示法 指针表示法一共需要两个结构体: struct ArcNode //定义边表结点 { int adjvex: //邻接点域 ArcNode* next; }; struct VertexNode //定义顶点表结点 { int vertex; ArcNode* firstedge; }; 每个节点对应一个VertexNode,其firstedge指向边表(与当前节点邻

Codeforces Round #460 (Div. 2)_D. Substring_[dp][拓扑排序]

题意:一个有向图,每个结点 被赋予一个小写字母,一条路径的value等与这条路径上出现次数最多的字母的数目,求该图的最大value 比赛时,用dfs超时,看官方题解用的dp和拓扑排序,a--z用0-25表示,用dp[i][j]表示以第i个结点结尾的路径上第j个字母出现的次数 拓扑排序每排到一个点,就用该点的dp去更新与它相邻点的dp,最开始入度为0的点特殊处理了一下,dp过程中同步更新结果res 也复习了一下拓扑排序 #include<iostream> #include<cstdio&

拓扑排序,逻辑开关

拓扑排序 是的,这个名字很怪.我们可以找一个直观的名字去描述拓扑排序,这个名字叫做不包含强连通子图.类似于不能出现1是2的父亲,2是1的父亲(这是一个很奇葩的例子)那么这种含有强连通子图,那么就倒霉了,他就不叫拓扑图,那么就无法用来dp了(这个后面会说) 那么如果没有类似于强连通子图(连通图),那么就恭喜你,这道题就可以使用dp来进行解决了,如果你的dp不熟悉,那么也可以另找退路,类似于记忆化搜索解决(这个后面会说到). 拓扑作用 如果你想使用dp解决在图中的题目,你必须保证此图是拓扑图. 如果

拓扑排序讲解

在这里我们要说的拓扑排序是有前提的 我们在这里说的拓扑排序是基于有向无环图的!!!. (⊙o⊙)…我所说的有向无环图都知道是什么东西吧.. 如果不知道,我们下面先来来说说什么是有向无环图. 所谓有向无环图,顾名思义是不存在环的有向图(至于有向图是什么不知道的在前面我们有一个图论讲解上都有). 点的入度:以这个点为结束点的边数. 点的出度:以这个点为出发点的边的条数. 拓扑序就是对于一个节点的一个排列,使得(u,v)属于E,那么u一定出现在v的前面.然而拓扑排序就是一个用来求拓扑序的东西. 对于左

CSU 1804: 有向无环图(拓扑排序)

http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1804 题意:…… 思路:对于某条路径,在遍历到某个点的时候,之前遍历过的点都可以到达它,因此在这个时候对答案的贡献就是∑(a1 + a2 + a3 + ... + ai) * bv,其中a是之前遍历到的点,v是当前遍历的点. 这样想之后就很简单了.类似于前缀和,每次遍历到一个v点,就把a[u]加给a[v],然后像平时的拓扑排序做就行了. 1 #include <bits/stdc++.h>

7-9-有向图无环拓扑排序-图-第7章-《数据结构》课本源码-严蔚敏吴伟民版

课本源码部分 第7章  图 - 有向无环图拓扑排序 ——<数据结构>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑        本源码引入的文件  链接? Status.h.SequenceStack.c.ALGraph.c    

hihoCoder 1175:拓扑排序二

题目链接: http://hihocoder.com/problemset/problem/1175 题目难度:一星级(简单题) 今天闲来无事,决定刷一道水题.结果发现这道水题居然把我卡了将近一个钟头. 最后终于调通了.总结起来,原因只有一个:不够仔细. 思路不用细说了,就是拓扑排序的简单应用.然而,一些不起眼的细节才是让你掉坑里的真正原因. 猜猜哪儿可能出bug? // A simple problem, but you can't be too careful with it. #inclu

hdu1285(拓扑排序)

这道题要求没有输赢关系的两个元素必须按照升序输出,有输赢关系的,赢得在输的前面,所以用队列或者栈来降低时间复杂度的优化过的拓扑排序会出错. 比如这组输入 5 3 1 2 2 3 4 5 至少我写的两种拓扑排序都wa了.但是不用队列或者栈来优化的话, 1.每次都从头至尾扫描一遍,找到一个没标记过的节点, 2.将它标记 3.然后删除从它出来的每条边. 重复这三个操作,加标记的次序,就是题目要的答案. 下面的代码中用到了队列,但只是用来保存答案而已.并没有用它优化的意思. #include <iost