动态规划 洛谷P4017 最大食物链计数——图上动态规划 拓扑排序
洛谷P4017 最大食物链计数
这是洛谷一题普及/提高-的题目,也是我第一次做的一题 图上动态规划/拓扑排序 ,我认为这题是很好的学习拓扑排序的题目。
在这题中,我学到了几个名词,入度,出度,及没有环的有向图必定有入度为0的点。通过与题干分析可知,入度为0就是最佳生产者,出度为0就是最佳消费者。题干的大意就是找出图中一共有几条食物链是从最佳生产者指向最佳消费者。
我在题解区学习了拓扑排序后的第一次题解,然而只过了一个测试点,一片WA声。。
1 //动态规划 洛谷P4017 最大食物链计数 2 #include<iostream> 3 #include<cmath> 4 #include<vector> 5 #include<queue> 6 using namespace std; 7 const int mod = 80112002; 8 const int MAX = 5e3 + 5; 9 int in[MAX], out[MAX];//两个数组 记录结点的入度和出度 10 vector<int>neigh[MAX]; //vector的二维数组 可以存放图的邻接关系 11 queue<int>que;//队列 每次将入度为0的点入队循环 12 int num[MAX];//记录每一个点的值 就是到这个点有几条路径 13 int ans; 14 int main() 15 { 16 int n, m; 17 int x, y;//用来输入被捕食和捕食的生物编号 18 cin >> n >> m;//n是生物数量 m是食物链数量 也可以说n就是结点数 m就是有向边的数量 19 for (int i = 0; i < m; ++i) 20 { 21 cin >> x >> y; 22 out[x]++, in[y]++;//图上是由x-->y所以入度y++ 出度x++ 23 neigh[x].push_back(y);//存放x指向的结点 便于后面遍历 24 } 25 26 for (int i = 1; i <= n; ++i) 27 { 28 //遍历寻找入度为0的点 也就是最佳生产者 加入队列 29 if (!in[i]) 30 { 31 num[i] = 1;//将入度为0的点的num设为1 自己到自己有一条路径 32 que.push(i); 33 break; 34 } 35 } 36 int temp; 37 while (!que.empty()) 38 { 39 temp = que.front(); 40 que.pop(); 41 //遍历这个结点所有连接的结点 42 for (int i = 0; i < neigh[temp].size(); ++i) 43 { 44 //把这些连接点的入度都减1 并且让他们的num值都加上前一个结点的num值 45 in[neigh[temp][i]]--; 46 num[neigh[temp][i]] = (num[temp] + num[neigh[temp][i]]) % mod; 47 if (in[neigh[temp][i]] == 0) 48 que.push(neigh[temp][i]);//如果入度为0 就把这个结点加入队列 49 } 50 } 51 for (int i = 1; i <= n; ++i) 52 { 53 //寻找出度为0的结点 出度为0说明就是最佳消费者 也就是最后一个终点结点 54 if (out[i] == 0) 55 { 56 ans = num[i]; 57 break; 58 } 59 60 } 61 cout << ans; 62 63 return 0; 64 65 }

经过我的思考与debug,我发现,不一定只有一个最佳生产者,也不一定只有一个最佳消费者,所以起点和终点应该有很多个,所以ans应该累加,起始队列的添加应该遍历完数组,可能添加多个起点。
修改后的代码:
1 //动态规划 洛谷P4017 最大食物链计数 2 #include<iostream> 3 #include<cmath> 4 #include<vector> 5 #include<queue> 6 using namespace std; 7 const int mod = 80112002; 8 const int MAX = 5e3 + 5; 9 int in[MAX], out[MAX];//两个数组 记录结点的入度和出度 10 vector<int>neigh[MAX]; //vector的二维数组 可以存放图的邻接关系 11 queue<int>que;//队列 每次将入度为0的点入队循环 12 int num[MAX];//记录每一个点的值 就是到这个点有几条路径 13 int ans; 14 int main() 15 { 16 int n, m; 17 int x, y;//用来输入被捕食和捕食的生物编号 18 cin >> n >> m;//n是生物数量 m是食物链数量 也可以说n就是结点数 m就是有向边的数量 19 for (int i = 0; i < m; ++i) 20 { 21 cin >> x >> y; 22 out[x]++, in[y]++;//图上是由x-->y所以入度y++ 出度x++ 23 neigh[x].push_back(y);//存放x指向的结点 便于后面遍历 24 } 25 26 for (int i = 1; i <= n; ++i) 27 { 28 //遍历寻找入度为0的点 也就是最佳生产者 加入队列 29 if (!in[i]) 30 { 31 num[i] = 1;//将入度为0的点的num设为1 自己到自己有一条路径 32 que.push(i);//入度为0的点有很多个 33 34 } 35 } 36 int temp; 37 while (!que.empty()) 38 { 39 temp = que.front(); 40 que.pop(); 41 //遍历这个结点所有连接的结点 42 for (int i = 0; i < neigh[temp].size(); ++i) 43 { 44 //把这些连接点的入度都减1 并且让他们的num值都加上前一个结点的num值 45 in[neigh[temp][i]]--; 46 num[neigh[temp][i]] = (num[temp] + num[neigh[temp][i]]) % mod; 47 if (in[neigh[temp][i]] == 0) 48 que.push(neigh[temp][i]);//如果入度为0 就把这个结点加入队列 49 } 50 } 51 for (int i = 1; i <= n; ++i) 52 { 53 //寻找出度为0的结点 出度为0说明就是最佳消费者 也就是最后一个终点结点 54 if (out[i] == 0) 55 { 56 ans = (ans + num[i]) % mod; 57 //出度为0的点也可能有很多个 不能break 要累加答案 58 } 59 60 } 61 cout << ans; 62 63 return 0; 64 65 }
完美通过

我们可以从这题中学习到拓扑排序的一个模板。
入度in[N] 出度out[N]的初始化数值 通过二维vector<int> neigh[N]来存储邻接图 num[N]来存储每个结点的答案数值 遍历入度来找到入度为0的点添加至队列queue<int>q 遍历队列的基本模板 while (!que.empty()) { temp = que.front(); que.pop(); //遍历这个结点所有连接的结点 for (int i = 0; i < neigh[temp].size(); ++i) { //把这些连接点的入度都减1 并且让他们的num值都加上前一个结点的num值 in[neigh[temp][i]]--; num[neigh[temp][i]] = (num[temp] + num[neigh[temp][i]]) % mod; if (in[neigh[temp][i]] == 0) que.push(neigh[temp][i]);//如果入度为0 就把这个结点加入队列 } } 最后遍历out数组找到出度为0的点 num值累加为ans即为但答案。

浙公网安备 33010602011771号