最大食物链计数(记忆化搜索/拓扑排序)

文章讲述了如何通过拓扑排序算法来计算图中的食物链数量。起点是入度为0的生产者,终点是出度为0的捕食者。给出了两种方法,一种是直接拓扑排序更新路径数,另一种是深度优先搜索(DFS),并讨论了在边数量多时使用记忆化搜索进行优化的情况。
摘要由CSDN通过智能技术生成

传送门

根据题意我们不难理解该题的意思就是求出一个图中的食物链一共有多少条,而我们知道食物链的起点是生产者不会捕食其他生物,终点时不会被捕食的捕食者,仔细想我们会发现,生产者的入度为0,而不会被捕食的捕食者的出度为0,从入度为0的点开始,这不是拓扑排序的特点吗,因此考虑使用拓扑排序

 如上图所示,以蓝色点为起点,红色点为终点,我们不难发现,到达红色点路径数取决于到达与之相连的2、3、4点,而到达它们的路径数又取决于与之相连的点,以此类推

我们假设起点的路径数为1,那么在拓扑排序的过程中,我们只需将与当前相连的点的路径数+=当前点的路径数即可

第一轮:删除 1 号蓝色点,1 号蓝色点可以到的点(2 号点、3 号点)都加 1

第二轮:删除 2 号点,2 号点可以到的点(3 号点、5 号红色点)都加 1。此时 3 号点答案为2,5 号点答案为 1

第三轮:删除 3 号点,3号点可以到的点(4 号点、5 号红色点)都加 2。此时 5 号点答案为 3,4 号点答案为 2

第四轮:最后删除 4 号点,4 号点可以到的点(5 号红色点)加 2,此时 5 号点答案为 5

可见全图只有 5 号一个红色点,那么答案就是 5 号点的答案———— 5 了

那么代码实现就很简单了!(题解参考自

#include<iostream>
#include<vector>
#include<stack>

using namespace std;

const int MAX=5e4,mod=80112002;

vector<int>mapp[MAX];
int arr1[MAX];//记录各个顶点的入度 
int arr2[MAX];//出度 
int ans[MAX];//以ans[i]为终点的食物链数
int n,m;

void fun(){
	stack<int>stk;
	for(int i=1;i<=n;i++){
		if(arr1[i]==0){
			stk.push(i);//将入度为0的点入栈 
			ans[i]=1;
		}
	}
	while(!stk.empty()){
		int cur=stk.top();
		stk.pop();
		int sizee=mapp[cur].size();
		for(int i=0;i<sizee;i++){
			int v=mapp[cur][i];
			ans[v]=(ans[v]+ans[cur])%mod;//注意取模
			arr1[v]--;
			if(arr1[v]==0){//入度为0 
				stk.push(v);
			}
		}
	}
}

int main(){
	int res=0;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		mapp[x].push_back(y);
		arr1[y]++;//入度
		arr2[x]++; //出度
	}
	fun();
	for(int i=1;i<=n;i++){
		if(arr2[i]==0){
			res=(res+ans[i])%mod;//注意可能有多个出度为0的点 
		}
	}
	cout<<res<<endl;
	return 0;
} 

当然像这种求解从起点开始到终点有多少条路径的题目也可以使用DFS来做

只需循环遍历起点进行DFS,只要到达一次终点就ans++即可

#include<iostream>
#include<vector>

using namespace std;

const int MAX=5e4;

vector<int>mapp[MAX];
int arr1[MAX];//记录各个顶点的入度 
int arr2[MAX];//出度 
int n,m,ans;

void dfs(int cur){
	if(arr2[cur]==0){//终点为出度为0的点 
		ans++;
		return ;
	}
	int sizee=mapp[cur].size();
	for(int i=0;i<sizee;i++){
		dfs(mapp[cur][i]);
	}
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		mapp[x].push_back(y);
		arr1[y]++;
		arr2[x]++; 
	}
	for(int i=1;i<=n;i++){
		if(arr1[i]==0){//从入度为0的顶点开始遍历 
			dfs(i);
		}
	}
	cout<<ans<<endl;
	return 0;
} 

 但是该题边的数量很多,因此会超时

我们考虑用记忆化搜索进行优化

#include<iostream>
#include<vector>
#include<stack>

using namespace std;

const int MAX=5e4,mod=80112002;

vector<int>mapp[MAX];
int arr1[MAX];//记录各个顶点的入度 
int arr2[MAX];//出度 
int book[MAX];//记忆数组,记录从i到终点有多少条路径 
int n,m;

int dfs(int cur){
	if(arr2[cur]==0)return 1;//出度为0的点 
	if(book[cur])return book[cur];//记忆
	int sizee=mapp[cur].size();//临接边数
	int sum=0;
	for(int i=0;i<sizee;i++){
		sum=(sum+dfs(mapp[cur][i]))%mod;
	}
	book[cur]=sum;
	return book[cur];
}

int main(){
	int res=0;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		mapp[x].push_back(y);
		arr1[y]++;//入度
		arr2[x]++; //出度
	}
	for(int i=1;i<=n;i++){
		if(arr1[i]==0){
			res=(res+dfs(i))%mod;//注意可能有多个入度为0的点 
		}
	}
	cout<<res<<endl;
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZZWWWFFF_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值