拓扑排序能够在线性的时间复杂度 O(n + m) 内完成求出拓扑序的操做,对象是有向无环图。
拓扑图的性质以下:c++
拓扑排序,能够支持如下操做:算法
#include <bits/stdc++.h> using namespace std; int const N = 1e5 + 10; int e[N], ne[N], h[N], idx, d[N]; int n, m; vector<int> ans; // 创建邻接表 void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } // 拓扑排序 void top_sort() { queue<int> q; // 维护一个队列 for (int i = 1; i <= n; ++i) if (!d[i]) q.push(i); // 把入度为0的点加入队列 // 当队列不为空时 while (q.size()) { auto t = q.front(); // 取队头 q.pop(); // 队头出队 ans.push_back(t); // 把这个数字放入答案序列 for (int i = h[t]; i != -1; i = ne[i]) // 枚举全部队头元素相邻的元素 { int j = e[i]; d[j]--; // 队头元素出队至关于把与队头元素相连的元素的入度减一 if (!d[j]) q.push(j); // 把入度为0的元素放入队列 } } if (ans.size() == n) // 输出答案序列 { for (auto a: ans) printf("%d ", a); } else cout << "-1"; } int main() { cin >> n >> m; // 输入点数和边数 memset(h, -1, sizeof h); // 初始化h for (int i = 0; i < m; ++i) // 读入每条边 { int a, b; scanf("%d %d", &a, &b); add(a, b); // 把b插入a的边表 d[b]++; // b的入度加一 } top_sort(); return 0; }
#include <bits/stdc++.h> using namespace std; int const N = 1e5 + 10; int e[N], ne[N], h[N], idx, d[N]; int n, m; vector<int> ans; // 创建邻接表 void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } // 拓扑排序 void top_sort() { priority_queue<int, vector<int>, greater<int>> q; // 这里是求字典序最小的拓扑序,若是求字典序最大的,那么改为 priority_queue<int, vector<int>, less<int>> q; for (int i = 1; i <= n; ++i) if (!d[i]) q.push(i); // 把入度为0的点加入队列 // 当队列不为空时 while (q.size()) { auto t = q.top(); // 取队头 q.pop(); // 队头出队 ans.push_back(t); // 把这个数字放入答案序列 for (int i = h[t]; i != -1; i = ne[i]) // 枚举全部队头元素相邻的元素 { int j = e[i]; d[j]--; // 队头元素出队至关于把与队头元素相连的元素的入度减一 if (!d[j]) q.push(j); // 把入度为0的元素放入队列 } } for (int i = 0; i < ans.size(); ++i) { cout << ans[i]; if (i != ans.size() - 1) cout << " "; } cout << endl; } int main() { while (scanf("%d%d", &n, &m) != EOF) { memset(d, 0, sizeof d); ans.clear(); idx= 0; memset(h, -1, sizeof h); // 初始化h for (int i = 0; i < m; ++i) // 读入每条边 { int a, b; scanf("%d %d", &a, &b); add(a, b); // 把b插入a的边表 d[b]++; // b的入度加一 } top_sort(); } return 0; }
HDU2857 逃生
题意: t个测试样例,每一个测试样例给出n和m,表示n个点、m条边。下面m行给出m条有向边信息a b,表示a->b。求出拓扑序,要求当拓扑序不惟一时,使得序号小的在前。
题解: 让编号小的尽可能靠前,这个意思不是字典序最小的拓扑序。解法:反向建图,优先队列(大顶堆)求字典序最大的序列,倒着输出即为本题答案。理解:看上图,咱们从1开始走,邻接点3和4,咱们不知道后面还有个2,因此不知道3和4先选谁,故正向寻找是错的。既然要求编号小的尽可能靠前,那咱们能够考虑把编号大的放到后面去。
咱们反向走,从最后面往前走,优先走编号大的,也就是字典序最大。最后把序列倒着输出,如此,就知足了本题。微信
#include <bits/stdc++.h> using namespace std; int const N = 1e5 + 10; int e[N], ne[N], h[N], idx, d[N]; int n, m, t; vector<int> ans; // 创建邻接表 void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } // 拓扑排序 void top_sort() { priority_queue<int, vector<int>, less<int>> q; // 维护一个从大到小的优先队列 for (int i = 1; i <= n; ++i) if (!d[i]) q.push(i); // 把入度为0的点加入队列 // 当队列不为空时 while (q.size()) { auto t = q.top(); // 取队头 q.pop(); // 队头出队 ans.push_back(t); // 把这个数字放入答案序列 for (int i = h[t]; i != -1; i = ne[i]) // 枚举全部队头元素相邻的元素 { int j = e[i]; d[j]--; // 队头元素出队至关于把与队头元素相连的元素的入度减一 if (!d[j]) q.push(j); // 把入度为0的元素放入队列 } } reverse(ans.begin(), ans.end()); // 反转输出 for (int i = 0; i < ans.size(); ++i) { cout << ans[i]; if (i != ans.size() - 1) cout << " "; } cout << endl; } int main() { cin >> t; while (t--) { scanf("%d%d", &n, &m); memset(d, 0, sizeof d); ans.clear(); idx= 0; memset(h, -1, sizeof h); // 初始化h for (int i = 0; i < m; ++i) // 读入每条边 { int a, b; scanf("%d %d", &a, &b); add(b, a); // 把b插入a的边表 d[a]++; // b的入度加一 } top_sort(); } return 0; }
Codeforces Round #656 (Div. 3) E. Directing Edges
题意: 给定t个测试样例,每一个测试样例给定n和m,n为图的点数,m为图的边数,给定m条边,每条边为op, a, b。若是op == 1,表示有一条有向边a -> b;若是op == 0,表示a和b之间有一条无向边。如今要求把无向边变成有向边(把a--b变成a->b或b->a),使得最后这m条边没有环。\(\sum_{i=1}^n n、m\) ~ 2e5
题解: 题意能够转换为如今有一张有向图,要求向这张有向图内加入有向边,使得这张有向图没有环,求加边的方法。所以,能够先作一次拓扑排序,求出拓扑序,若是没有拓扑序,说明已经存在环;不然,只须要加入拓扑序小的指向拓扑序大的边便可。(由于想要造成环,必须有回路,则必须a->b,同时b->a,一旦出现拓扑序,说明存在a->b,不存在b->a,所以只要不加入b->a,则不可能出现环)
代码以下:less
#include<bits/stdc++.h> using namespace std; int const N = 2e5 + 10; int t, n, m; set<int> point; int e[N * 2], ne[N * 2], idx, h[N]; int d[N], sorted[N]; vector<pair<int, int> > undirect, direct; vector<int> ans; struct Edge{ int op, u, v; }; vector<Edge> E; void top_sort() { queue<int> q; for (int i = 1; i <= n; ++i) { if (!d[i]) { q.push(i); } } while (q.size()) { auto t = q.front(); q.pop(); ans.push_back(t); for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; d[j]--; if (!d[j]) { q.push(j); } } } // 记录一下拓扑序内的点的顺序 for (int i = 0; i < ans.size(); ++i) { sorted[ans[i]] = i + 1; } return ; } void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } int main() { cin >> t; while (t--) { memset(h, -1, sizeof h); memset(d, 0, sizeof d); idx = 0; E.clear(); memset(sorted, 0, sizeof sorted); ans.clear(); cin >> n >> m; for (int i = 0, op, a, b; i < m; ++i) { scanf("%d%d%d", &op, &a, &b); E.push_back({op, a, b}); if (op == 1) { add(a, b); d[b]++; } } top_sort(); if (ans.size() != n) { cout << "NO\n"; continue; } cout << "YES\n"; for (int i = 0; i < E.size(); ++i) { if (E[i].op == 1) { cout << E[i].u << " " << E[i].v << endl; } else { // 按照拓扑序内顺序小的指向顺序大的 if (sorted[E[i].u] < sorted[E[i].v]) cout << E[i].u << " " << E[i].v << endl; else cout << E[i].v << " " << E[i].u << endl; } } } return 0; }
acwing1192奖金
公司按照每一个人的贡献给每一个人发奖金。奖金存在M对关系,每对关系为a,b,表示a的奖金比b高。每位员工工资最少为100元,问最少须要发多少奖金。测试
/* 本题是差分约束的简化版,造成的边只有正权边 若是存在正环那么无解,换言之,若是不存在拓扑序则无解,所以可使用拓扑排序来判断 若是有解,求出拓扑序后,直接按照拓扑序更新最长路便可 */ #include<bits/stdc++.h> using namespace std; int const N = 1e4 + 10, M = 2e4 + 10; int n, m; int din[N], dis[N]; int e[M], ne[M], h[N], idx; vector<int> ans; // 拓扑排序 bool topsort() { queue<int> q; for (int i = 1; i <= n; ++i) if (!din[i]) q.push(i); while (q.size()) { auto t = q.front(); q.pop(); ans.push_back(t); for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; din[j]--; if (!din[j]) q.push(j); } }v return ans.size() == n; } void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } int main() { // 建图 memset(h, -1, sizeof h); cin >> n >> m; for (int i = 0; i < m; ++i) { int a, b; scanf("%d %d", &a, &b); add(b, a); din[a] ++; } // 拓扑排序判断是否有解 if (!topsort()) { printf("Poor Xed\n"); return 0; } // 按照拓扑排序更新最长路 for (int i = 1; i <= n; ++i) dis[i] = 100; for (int i = 0; i < n; ++i) { int t = ans[i]; for (int j = h[t]; ~j; j = ne[j]) { int k = e[j]; dis[k] = max(dis[k], dis[t] + 1); } } // 计算答案 int ans = 0; for (int i = 1; i <= n; ++i) ans += dis[i]; cout << ans << endl; return 0; }
acwing456车站分级
题意: 一条单向的铁路线上,依次有编号为1, 2, …, n 的n个火车站。每一个火车站都有一个级别,最低为1级。现有若干趟车次在这条线路上行驶,每一趟都知足以下要求:若是这趟车次停靠了火车站x,则始发站、终点站之间全部级别大于等于火车站x的都必须停靠。(注意:起始站和终点站天然也算做事先已知须要停靠的站点)
例如,下表是5趟车次的运行状况。
其中,前4趟车次均知足要求,而第5趟车次因为停靠了3号火车站(2级)却未停靠途经的6号火车站(亦为2级)而不知足要求。
现有m趟车次的运行状况(所有知足要求),试推算这n个火车站至少分为几个不一样的级别。1≤n,m≤1000
题解: 很明显,不停靠的站点的优先级必定比停靠的站点的优先级要小,所以不停靠的站点的优先级最小为1,且停靠的站点的优先级>=不停靠的站点的优先级+1,则本题能够转换为一个差分约束问题,且边权大于等于0。(这里不须要tarjan判断是否有正环,由于明确了有解,不可能出现正环,因此直接拓扑排序求拓扑序(tarjan的目的也是缩点完求拓扑序))。本题的另外一个难点在于建图,若是直接把不停靠的站点向停靠的站点连一条边,那么建图的复杂度为O(mn^2^)。对于一个二分图,左边的每一个点都须要向右边每一个点连一条边的建图模型来讲,能够设置一个虚拟节点,而后使得左边每一个点连向虚拟节点,虚拟节点再向右边每一个点连边。这样就把O(n ^ 2)优化到O(n)。
优化
/* 本题是一道差分约束问题,对于每一辆车停靠的站点的优先级必定比没停靠的站点的优先级要高,所以 全部未停靠的站点均可以连一条边到停靠的站点。 可是若是这样建图将会建边平方条,1e8,超时 考虑简单的建边方法:对于每辆车创建一个虚拟节点,这个虚拟节点能够认为是未停靠的车站的最高优先级, 那么对于每一个关系b>=a+1则能够从原来的a->b建权值为1的边变化为a向虚拟节点ver建一条边权为0的边,再从ver到b建一条 边权为1的边,这样子就能够把平方的边数降到线性,每辆车最多创建2000条边,总共2e6条边 建图后,因为本题特色保证了必定是一张DAG,因此要求最长路不须要判正环,直接拓扑排序后获得顺序,而后dp求最长路便可 */ #include<bits/stdc++.h> using namespace std; int const N = 2e3 + 10, M = 2e6 + 10; int n, m; int e[M], ne[M], h[N], w[M], idx; int din[N], st[N], dis[N]; vector<int> ans; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } // 拓扑排序 void topsort() { queue<int> q; for (int i = 1; i <= n + m; ++i) if (!din[i]) q.push(i); while (q.size()) { auto t = q.front(); q.pop(); ans.push_back(t); for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; din[j]--; if (!din[j]) q.push(j); } } } int main() { memset(h, -1, sizeof h); cin >> n >> m; for (int i = 1; i <= m; ++i) { // 建图(加入虚节点,平方->线性) memset(st, 0, sizeof st); int cnt, start = n, end = 1, ver = n + i; // start记录最小的点,end记录最大的点,ver记录虚拟节点 cin >> cnt; for (int i = 0; i < cnt; ++i) { int t; cin >> t; st[t] = 1; start = min(start, t); end = max(end, t); } // 把a->b的边拆成a->ver和ver->b for (int j = start; j <= end; ++j) { if (st[j]) add(ver, j, 1), din[j]++; else add(j, ver, 0), din[ver]++; } } // 拓扑排序获得更新的顺序 topsort(); // dp求最大值 for (int i = 1; i <= n; ++i) dis[i] = 1; for (int i = 0; i < ans.size(); ++i) { int k = ans[i]; for (int j = h[k]; ~j; j = ne[j]) { int t = e[j]; dis[t] = max(dis[t], dis[k] + w[j]); } } int res = 0; for (int i = 1; i <= n; ++i) res = max(res, dis[i]); cout << res << endl; return 0; }
acwing164可达性统计
给定一张N个点M条边的有向无环图,分别统计从每一个点出发可以到达的点的数量。第一行两个整数N,M,接下来M行每行两个整数x,y,表示从x到y的一条有向边。输出共N行,表示每一个点可以到达的点的数量。1≤N,M≤30000spa
/* 由于是有向无环图,因此当前状态只影响后面的状态,因此可使用dp的思想处理 所以,先作一次拓扑排序,获得拓扑序,然后使用逆拓扑序倒着向前更新每一个点的可达性 具体更新的时候可使用一个bitset便可维护当前每一个点的可达状态 */ #include <bits/stdc++.h> using namespace std; int const N = 3e4 + 10; int e[N], ne[N], idx, h[N]; int n, m, d[N]; bitset<N> f[N]; vector<int> ans; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } void top_sort() { queue<int> q; for (int i = 1; i <= n; ++i) if (!d[i]) q.push(i); while (!q.empty()) { auto t = q.front(); q.pop(); ans.push_back(t); for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; d[j]--; if (!d[j]) q.push(j); } } return ; } int main(){ memset(h, -1, sizeof h); cin >> n >> m; for (int i = 1, a, b; i <= m; ++i) { scanf("%d%d", &a, &b); d[b] ++; add(a, b); } // 求出拓扑序 top_sort(); // 倒着更新求出可达性 for (int i = ans.size() - 1; i >= 0; --i) { int j = ans[i]; // 当前这个点 f[j][j] = 1; // 当前这个点到本身是可达的 for (int k = h[j]; ~k; k = ne[k]) { int t = e[k]; // j可以遍历到的点是t f[j] |= f[t]; // j的状态受t影响 } } for (int i = 1; i <= n; ++i) cout << f[i].count() << endl; return 0; }