拓扑排序介绍及其应用

拓扑排序简介

拓扑排序是一种图论排序方式,要了解拓扑排序,首先要了解两个图论概念:入度出度。在有向无环图 D A G DAG DAG)中,一个点是多少弧的弧头称为入度,是多少弧的弧尾是出度
而作拓扑排序时,只需做以下步骤:

  1. 将入度为0的点存在一个队列内
  2. 每次取出一个点,将其能达到的点的入度减 1 1 1
  3. 将上一步操作中入度降为 0 0 0的点放在队尾
  4. 删除第 2 2 2步取出的那个点

最后,被删除的点的先后顺序就是这个图的拓扑序。
例如下面这个有向无环图的拓扑序便是: 4 → 3 → 6 → 5 → 1 → 2 4\rightarrow3\rightarrow6\rightarrow5\rightarrow1\rightarrow2 436512

下面附上一般拓扑排序的部分代码

void topo(){//拓扑排序 
	for(int i=1;i<=n;i++)
	{
		if(in[i]==0)
		{
			q.push(i);
		}
	}
	while(!q.empty())
	{
		int p=q.front(); q.pop();
		for(int i=head[p];i>0;i=nxt[i])
		{
			int go=to[i];
			in[go]--;
			if(in[go]==0)
			q.push(go);
		}
	}
}

其中 i n [ i ] in[i] in[i]用来存储入度, g o [ i ] go[i] go[i]用来存储出度。

应用

我们看这样一道例题:

最大食物链计数

题目背景

你知道食物链吗?Delia 生物考试的时候,数食物链条数的题目全都错了,因为她总是重复数了几条或漏掉了几条。于是她来就来求助你,然而你也不会啊!写一个程序来帮帮她吧。

题目描述

给你一个食物网,你要求出这个食物网中最大食物链的数量。

(这里的“最大食物链”,指的是生物学意义上的食物链,即最左端是不会捕食其他生物的生产者,最右端是不会被其他生物捕食的消费者。)

Delia 非常急,所以你只有 1 1 1 秒的时间。

由于这个结果可能过大,你只需要输出总数模上 80112002 80112002 80112002 的结果。

输入格式

第一行,两个正整数 n 、 m n、m nm,表示生物种类 n n n 和吃与被吃的关系数 m m m

接下来 m m m 行,每行两个正整数,表示被吃的生物A和吃A的生物B。

输出格式

一行一个整数,为最大食物链数量模上 80112002 80112002 80112002 的结果。

样例 #1

样例输入 #1

5 7
1 2
1 3
2 3
3 5
2 5
4 5
3 4

样例输出 #1

5

提示

各测试点满足以下约定:


首先不难看出食物网是一张有向无环图 D A G DAG DAG),可以考虑通过 d p dp dp来求解,我们由相对若的生物指向相对强的生物来建边,如何建边可以考虑用邻接矩阵或者是我之前文章中提到的链式前向星算法引用链接
而状态转移方程并不难想,用数组 d p [ i ] dp[i] dp[i]存储以 i i i为终点的食物链条数,如果以 i i i为弧尾的弧的弧头是 j j j,那么我们有(在本题中注意取模)
d p [ j ] + = d p [ i ] dp[j]+=dp[i] dp[j]+=dp[i]
而最终的答案 a n s = ∑ d p [ i ] ( w h i l e   g o [ i ] = 0 ) ans=\sum dp[i](while~go[i]=0) ans=dp[i](while go[i]=0
最后附上本题解答

#include<bits/stdc++.h>
using namespace std;

queue<int> q;

int cnt=0,n,m;
int nxt[500005],to[500005],head[5005],dp[5005],in[5005];
bool has_son[5005]; 
const int mod=80112002;
long long ans=0;

void add(int u,int v){//链式前向星 
	nxt[++cnt]=head[u];
	head[u]=cnt;
	to[cnt]=v;
	has_son[u]=true;
}

void topo(){//拓扑排序 
	for(int i=1;i<=n;i++)
	{
		if(in[i]==0)
		{
			q.push(i);
			dp[i]=1;
		}
	}
	while(!q.empty())
	{
		int p=q.front(); q.pop();
		for(int i=head[p];i>0;i=nxt[i])
		{
			int go=to[i];
			dp[go]=(dp[go]+dp[p])%mod;
			in[go]--;
			if(in[go]==0)
			q.push(go);
		}
	}
}

int read(){
	int x=0,f=1;
    char c=getchar();
    if(c<'0'||c>'9'){
    	if(c=='-') f=-1;
    	c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}


int main(){
      n=read(),m=read(); 
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		add(u,v);
		in[v]++;
	}
	topo();
	for(int i=1;i<=n;i++){
		if(!has_son[i]){
			ans=(ans+dp[i])%mod;
		}
	}
	cout<<ans;
	return 0;
}
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值