

注意:本编标红处是可以直接纳为己用的经验条哦~
OJ题来源:洛谷
OJ题名:摄像头
OJ题归属:图论基础【拓扑排序】
解题算法:拓扑排序
经验总结:存图的那些顶点有的时候并不是依次顺位的,我们要开好足够大的空间,用数组标记一下那些顶点,这样可以提高一点效率,同时能更好的区分(比如此题的摄像头与可监视区域,如果单单只靠入度来区分,是不够的,此时有了标记数组,那么摄像头的属性就要多出一个,就有了区分的作用)。
cpp#include<iostream> #include<queue> #include<vector> using namespace std; const int N = 510; int n; vector<int> edges[N]; int in[N]; bool st[N]; int main() { cin >> n; for (int i = 1; i <= n; i++) { int x, y, z; cin >> x >> y; st[x] = true; while (y--) { cin >> z; edges[x].push_back(z); in[z]++; } } queue<int> q; for (int i = 1; i <= 500; i++) { if (in[i] == 0 && st[i]) q.push(i); } while (q.size()) { auto x = q.front(); q.pop(); for (auto& e : edges[x]) { in[e]--; if (in[e] == 0 && st[e]) q.push(e); } } int ret = 0; for (int i = 1; i <= 500; i++) { // 可被监视的地点且没有摄像头的区域,他的入度也是有的,所以特判的时候, // 入度不是唯一标准,还要是摄像头 if (in[i] > 0 && st[i]) ret++; } if (ret == 0) cout << "YES" << endl; else cout << ret << endl; return 0; }
OJ题来源:洛谷
OJ题名:最大食物链计数
OJ题归属:图论基础【拓扑排序】
解题算法:路径类dp + 拓扑排序
经验总结:此题的学习加深了对于dp的底层理解,在上一章动态规划的章节中,我们常常是在二维数组中进行dp,其实那种是考虑了有效和无效数据的大杂烩,而在有向图上做路径类dp,就是基于有效数据进行的dp,那些无效数据是没有被考虑上的。
在有向图上做dp其实是有一个难点的,就是五板斧中的填表顺序,在有向图上做dp不像在二维数组那样格式固定化,但是之前学习的拓扑排序其实就是量身定制适配于图的路径dp的填表顺序,解释一下:当x从队列中拿出来,那么x的dp表的位置已经跟新完毕,那么,依靠x推演的dp表格子就可以跟着跟新了。
cpp#include<iostream> #include<vector> #include<queue> using namespace std; const int N = 5010, MOD = 80112002; int n, m; vector<int> edges[N]; // 存图 int in[N], out[N]; // 存入度、出度 ---入度用于拓扑排序,出度用于找最后那个/那几个纯消费者 int f[N]; // 表示从起点到 i 位置的方案数 int main() { cin >> n >> m; for (int i = 1; i <= m; i++) { int x, y; cin >> x >> y; edges[x].push_back(y); in[y]++; out[x]++; } //拓扑排序 + dp queue<int> q; for (int i = 1; i <= n; i++) { if (in[i] == 0) { // dp 初始化 f[i] = 1; // 扔进队列 q.push(i); } } for (int i = 1; i <= n; i++) { auto x = q.front(); q.pop(); for (auto& e : edges[x]) { // x -> e && f[x]已经确定,依靠x跟新的可以跟新了 f[e] = (f[e] + f[x]) % MOD; in[e]--; if (in[e] == 0) q.push(e); } } int ret = 0; for (int i = 1; i <= n; i++) { if (out[i] == 0) { ret = (ret + f[i]) % MOD; } } cout << ret << endl; return 0; }
OJ题来源:洛谷
OJ题名:杂务
OJ题归属:图论基础【拓扑排序】
解题算法:动态规划 + 拓扑排序
经验总结:图上的动态规划的状态转移方程的跟新是分批次的,不像是以前动态规划章节一次性更新完一个空。在此题中,在将队头元素拿出队的时候,代表此时这个空已经把前置的那些格子中的所需最大值已经跟新完毕,此时加上该位置自己的值,该位置的完整结果就出来了,在枚举指向的孩子那,状态转移方程更新的是前置那些格子中的最大值,还没有考虑当前位置自己的值。
cpp#include<iostream> #include<vector> #include<queue> using namespace std; const int N = 10010; int n; vector<int> edges[N]; int f[N]; int in[N]; int len[N]; int main() { cin >> n; for (int i = 1; i <= n; i++) { int a, b; cin >> b >> len[b]; while (cin >> a, a) { edges[a].push_back(b); in[b]++; } } queue<int> q; for (int i = 1; i <= n; i++) { if (in[i] == 0) q.push(i); } int ret = 0; while (q.size()) { auto x = q.front(); q.pop(); // 此时 x 从队列中拿出来,x位置之前的工作花费时间最大值已经存与f[i]中 f[x] += len[x]; // 跟新好x位置的完整结果 ret = max(ret, f[x]); // 拿出完整结果中的最大值就是最终结果 for (auto& e : edges[x]) { // 跟新以x为前置工作的花费时间,分批次跟新出前置工作中花费时间最大值 f[e] = max(f[e], f[x]); //拓扑排序的一些操作 in[e]--; if (in[e] == 0) q.push(e); } } cout << ret << endl; return 0; }