一、试题讲解
https://www.cnblogs.com/CJYBlog/p/12198894.html
二、拓扑排序完整代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5005; //生物种类上限
const int M = 500005; //吃与被吃的关系数上限
const int MOD = 80112002; //最大食物链数量模
int n; //生物种类
int m; //吃与被吃的关系数
int ans; //为最大食物链数量模上 80112002 的结果
vector<int> edge[N]; //保存DAG图的邻接表
queue<int> q; //广搜的队列
int f[N]; //每个生物种类的食物链最长值
int ind[N]; //每个生物种类的入度
int out[N]; //每个生物种类的出度
/**
总结:
1、谁向谁有一条有向边是需要强调的,x->y与 y->x是完全不同的。本题是说x被y吃掉,记录的是这个关系,
即x->y有一条有向边。
2、需要进行模的题,需要每一步操作结果时,都进行一次MOD操作,防止中间过程数据过大。
3、获得的所有食物链数量加在一起,再MOD,才是结果。
4、DAG+广度优先搜索= "拓扑排序" !!! !!!!目的:使得在搜到点x时,所有能达到点x的点,已经被搜过了。
5、拓扑排序思路:把入度为0的入到队列,然后找到它的所有出边,对接点入度-1,如果对接点入度为0,则再入队列。
6、每一个以当前结点为终止结点的路径条数,等于上一级的N个结点,每个结点条数的和。
7、在DAG的广度搜索中+动态规划是本题的难点,注意base case的理解。
8、入度为0的,是第一批入队列的,它们是食物链的底层(草根)。出度为0的是食物链的顶层,是老虎,鹰,鲨鱼等。
9、出度是否为0,是判断是不是再继续入队列的条件。
10、入度为0的所有结点,它们的食物链条数和就是答案
11、与深度优先不同,拓扑排序,需要同时维护出度和入度,出度在录入数据后,维护不变;入度随算法的深入,
一直在-1,直到为0.
*/
int main() {
cin >> n >> m;
//m种关系
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y; //x被y吃掉
out[x]++; //点x的出度+1
ind[y]++; //点y的入度+1
edge[x].push_back(y); //用邻接表记录下食物链的关系,x被y吃掉,由x向y引一条有向边
}
//找到所有入度为0的点,放入广度优先搜索的队列
for (int i = 1; i <= n; i++) if (!ind[i]) q.push(i), f[i] = 1;
//f[i]=1:base case,它到它的每个孩子都有一条出边,就是一条路径
//广度优先搜索DAG,就是拓扑排序的模板
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = 0; i < edge[x].size(); i++) { //遍历所有出边
int y = edge[x][i]; //目标结点
f[y] = (f[x] + f[y]) % MOD; //在计算f[y]之前, f[x]都是计算过的了。
//对接点入度-1,抹去这条入边
ind[y]--;
//如果入度为0,则入队列,准备处理它
if (!ind[y]) q.push(y);
}
}
//遍历所有结点,如果出度为0,描述
for (int i = 1; i <= n; i++) if (!out[i]) ans = (ans + f[i]) % MOD;
cout << ans << endl;
return 0;
}