動態規劃 洛谷P4017 最大食物鏈計數——圖上動態規劃 拓撲排序
阿新 • • 發佈:2022-04-02
洛谷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];//記錄每一個點的值 就是到這個點有幾條路徑 13int 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即為但答案。