拓扑排序 Toposort
对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点 u u u 和 v v v ,若边 < u , v > ∈ E ( G ) <u,v>∈E(G) <u,v>∈E(G),则u在线性序列中出现在 v v v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
拓扑排序删边操作步骤
- 统计所有点的入度
- 将入度为 0 的点输出 删去其所连的边
- 重复操作直到所有点删去
输出5
输出5 1
输出5 1 2
输出5 1 2 3
输出5 1 2 3 4
例题应用
排序 P1347
P1347排序 【拓扑排序模板题】 洛谷传送门
本题就是裸的tp。
题目描述
一个不同的值的升序排序数列指的是一个从左到右元素依次增大的序列,例如,一个有序的数列
A
,
B
,
C
,
D
A,B,C,D
A,B,C,D 表示
A
<
B
,
B
<
C
,
C
<
D
A<B,B<C,C<D
A<B,B<C,C<D。在这道题中,我们将给你一系列形如
A
<
B
A<B
A<B 的关系,并要求你判断是否能够根据这些关系确定这个数列的顺序。
输入格式
第一行有两个正整数
n
,
m
n,m
n,m,
n
n
n表示需要排序的元素数量,
2
≤
n
≤
26
2≤n≤26
2≤n≤26,第
1
1
1 到
n
n
n 个元素将用大写的
A
,
B
,
C
,
D
A,B,C,D
A,B,C,D… 表示。
m
m
m 表示将给出的形如
A
<
B
A<B
A<B 的关系的数量。
接下来有
m
m
m 行,每行有
3
3
3 个字符,分别为一个大写字母,一个 <
符号,一个大写字母,表示两个元素之间的关系。
输出格式
若根据前
x
x
x 个关系即可确定这
n
n
n 个元素的顺序 yyy..y
(如
A
B
C
ABC
ABC),输出
Sorted sequence determined after xxx relations: yyy...y.
若根据前 x x x 个关系即发现存在矛盾(如 A < B , B < C , C < A A<B,B<C,C<A A<B,B<C,C<A),输出
Inconsistency found after x relations.
若根据这 m m m 个关系无法确定这 n n n 个元素的顺序,输出
Sorted sequence cannot be determined.
(提示:确定 n n n 个元素的顺序后即可结束程序,可以不用考虑确定顺序之后出现矛盾的情况)
输入样例1
4 6
A<B
A<C
B<C
C<D
B<D
A<B
输出样例1
Sorted sequence determined after 4 relations: ABCD.
输入样例1
3 2
A<B
B<A
输出样例1
Inconsistency found after 2 relations.
输入样例1
26 1
A<Z
输出样例1
Sorted sequence cannot be determined.
//P1347 排序 拓扑排序解法
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define maxn 30
#define maxm 700
using namespace std;
int n,m,len,cnt,c;
int head[maxn],vis[maxn],dis[maxn];
//链式前向星head[] vis[]标记 dis[]拓扑链长度
int f[maxn],in[maxn],qq[maxn];
//f入度 in暂存入度 qq记录队列
struct node{
int to,next;
}e[maxm];
//链式表
void add(int u,int v){
cnt++;
e[cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}//加边
queue<int>q;//队列
//拓扑排序
void topo(){
//初始化
memset(dis,0,sizeof dis);
memset(vis,0,sizeof vis);
memset(qq,0,sizeof qq);
len=c=0;
for(int i=1;i<=n;i++){
in[i]=f[i]; //暂存入度初始化
if(in[i]==0){ //一开始入度==0 说明是根结点
q.push(i); //入队
vis[i]=1; //入队标记
dis[i]=1; //长度为1
}
}
while(!q.empty() ){
int t=q.front() ; q.pop() ; qq[++c]=t; //取队首
//链式前向星遍历
for(int i=head[t];i;i=e[i].next ){
int v=e[i].to ; //简记出点
if(vis[v]!=0) continue; //标记过了就跳过
//取最长链长度
dis[v]=max(dis[v],dis[t]+1);
len=max(len,dis[v]);
in[v]--;//入度--
if(in[v]==0){ //入度==0 入队
q.push(v);
vis[v]=1;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
char ch,u,v;
cin>>u>>ch>>v;
//特判1 :A<A 矛盾
if(u==v){
printf("Inconsistency found after %d relations.",i);
return 0;
}
add(u-64,v-64); //加边 由小点指向大点 以保证升序输出
f[v-64]++; //入度++
topo();
//特判2:最长链长度==n 符合条件输出
if(len==n){
printf("Sorted sequence determined after %d relations: ",i);
for(int j=1;j<=n;j++){
printf("%c",qq[j]+64);
}
printf(".\n");
return 0;
}
//特判3:存在环 矛盾
for(int j=1;j<=n;j++){
if(in[j]){ //入度!=0
printf("Inconsistency found after %d relations.",i);
return 0;
}
}
}
//特判4:无法构成 长为n的 链
printf("Sorted sequence cannot be determined.");
return 0;
}
也可以用Floyd求解
//P1347 排序 floyd解法
#include<iostream>
#include<cstdio>
#define inf 0x3f3f3f3f
#define maxn 30
using namespace std;
int n,m,dis[maxn][maxn],q[maxn];
//初始化
void stat(){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) dis[i][j]=inf;
}
}
}
//裸的floyd最短路
void floyd(){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main(){
scanf("%d%d",&n,&m);
stat();
for(int k=1;k<=m;k++){
char ch,u,v;
cin>>u>>ch>>v;
dis[u-64][v-64]=1;
//特判1 :A<A 矛盾
if(u==v) {
printf("Inconsistency found after %d relations.",k);
return 0;
}
floyd();
int anscnt=0;
for(int i=1;i<=n;i++){
int cnt1=0,cnt2=0;
for(int j=1;j<=n;j++){
if(i==j) continue;
//特判2:存在环 矛盾
if(dis[i][j]!=inf && dis[j][i]!=inf ){
printf("Inconsistency found after %d relations.",k);
return 0;
}
if(dis[i][j]!=inf) cnt1++; //能到达该点的个数
if(dis[j][i]!=inf) cnt2++; //该点能到达的个数
}
if(cnt1+cnt2==n-1) q[cnt2+1]=i, anscnt++; //该点可以被确定
}
if(anscnt==n){ //所有点都可以被确定 特判3:符合输出
printf("Sorted sequence determined after %d relations: ",k);
for(int j=1;j<=n;j++){
printf("%c",q[j]+64);
}
printf(".\n");
return 0;
}
}
//特判4:无法构成 长为n的 链
printf("Sorted sequence cannot be determined.");
return 0;
}
信息传递 P2661
本题就是求最小环。 具体思路:拓扑排序删边,删到最后入度为0的点就在环上,dfs搜索环的深度,记录最小环的大小。
题目描述
有
n
n
n 个同学(编号为
1
1
1 到
n
n
n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为
i
i
i 的同学的信息传递对象是编号为
T
i
T_i
Ti 的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入格式
共2行。
第1行包含
1
1
1个正整数
n
n
n,表示
n
n
n 个人。
第2行包含
n
n
n 个用空格隔开的正整数
T
1
,
T
2
.
.
.
,
T
n
T_1,T_2...,T_n
T1,T2...,Tn ,其中第
i
i
i 个整数
T
i
T_i
Ti 表示编号为
i
i
i 的同学的信息传递对象是编号为
T
i
T_i
Ti 的同学,
T
i
≤
n
T_i ≤n
Ti≤n 且
T
i
≠
i
T_i ≠ i
Ti=i。
输出格式
1
1
1个整数,表示游戏一共可以进行多少轮。
输入样例
5
2 4 2 3 1
输出样例
3
数据范围
对于
30
30%
30的数据,
n
≤
200
n ≤ 200
n≤200;
对于
60
60%
60的数据,
n
≤
2500
n ≤ 2500
n≤2500;
对于
100
100%
100的数据,
n
≤
200000
n ≤ 200000
n≤200000。
#include<iostream>
#include<cstdio>
#include<queue>
#define maxn 200005
using namespace std;
int n,head[maxn],in[maxn],vis[maxn],cnt;
//链式head[] 入度in[] 标记vis[]
int ans=1e9,dep;
//ans最小环 dep每一遍dfs的环的大小
struct node{
int to,next;
}e[maxn];
//加边
void add(int u,int v){
cnt++;
e[cnt].to =v;
e[cnt].next =head[u];
head[u]=cnt;
}
//搜索删完边后的最小环
void dfs(int k,int f){ //当前点k 终点f
dep++; //环大小++
for(int i=head[k];i;i=e[i].next ){
int v=e[i].to;
if(v!=f) dfs(v,f), vis[v]=1;
//判出点是否为终点 否则继续dfs并标记
}
}
//拓扑排序删边
void topo(){
queue<int>q;
for(int i=1;i<=n;i++){
if(in[i]==0){ //初始入度为 0 入队标记
q.push(i);
vis[i]=1;
}
}
while(!q.empty()){
int t=q.front() ; q.pop() ;//取队首
for(int i=head[t];i;i=e[i].next ){ //链式前向星遍历
int v=e[i].to; //标记出点
in[v]--; //出点的入度--
if(in[v]==0){ //入度为0 则入队标记
q.push(v); vis[v]=1;
}
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
add(i,x); //加边
in[x]++; //入度++
}
topo();
//删完边后 环上的点的vis仍为0
for(int i=1;i<=n;i++){
if(!vis[i]){ //对环上的点进行dfs
dep=0;
dfs(i,i); //起点i 终点i
ans=min(dep,ans); //取最小环
}
}
printf("%d",ans);
}