拓扑排序:
给定一张有向无环图,若由途中节点组成的序列A满足: 对于任意两点i,j,其边为(i,j),i在A中的位置都出现在y前面
比如:
则其其中一个拓扑序为 1 4 2 3 5
做法:
对于拓扑序的开头的点一定是没有被任何点指向的,因此我们求所有点的入度,将入度为0的点压入队列中,然后将队列中的点的所有子节点的入度-1,再次寻找所有入度为0的点,循环直到队列为空为止。
void add_edge(int fro, int to) { //链式向前星建边
Nxt[++cnt] = Head[fro];
To[cnt] = to;
Head[fro] = cnt;
in[to]++; //in: 入度
}
void Top_sort() {
queue<int> Q;
id = 0;
for (int i = 1; i <= n; i++) if (in[i] == 0) Q.push(i); //假如入度为0则入队
while (!Q.empty()) {
int now = Q.front(); //每一个取出
Q.pop();
topsort[++id] = now; //放入拓扑序中
for (int i = Head[now]; i; i = Nxt[i]) { //遍历其每一个子节点,入度-1
if (--in[To[i]] == 0) Q.push(To[i]); //假如子节点入度为0则入队
}
}
}
题目1:ch2101 可达性统计
题目:https://www.acwing.com/problem/content/166/
从每个点出发能到的点数=其所有子节点出发能到的点数之和+1
使用拓扑排序的话,我们就可以保证拓扑序列中某一个节点的子节点只出现在其右边,等处理完子节点之后使用其结果再处理该节点。
当然在你有啥大胆的想法之后可以看一下这幅图:
假如逆序遍历拓扑序,往前叠加结果的话,会出现1重复叠加到4的情况。
这时候可以使用STL中的bitset,将对每个点有贡献的点都压缩到一个状态中,叠加的时候使用异或就可以避免出现多次叠加的尴尬场面
代码:
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 30010;
int Head[maxn], Nxt[maxn], To[maxn];
int cnt, n, m, id, bcnt;
int topsort[maxn], in[maxn];
int ans[maxn];
bitset<maxn> s[maxn];
int *ver = To, *Next = Nxt, *head = Head, *deg = in, *a = topsort;
void add_edge(int fro, int to) { //链式向前星建边
Nxt[++cnt] = Head[fro];
To[cnt] = to;
Head[fro] = cnt;
in[to]++; //in: 入度
}
void Top_sort() {
queue<int> Q;
id = 0;
for (int i = 1; i <= n; i++) if (in[i] == 0) Q.push(i); //假如入度为0则入队
while (!Q.empty()) {
int now = Q.front(); //每一个取出
Q.pop();
topsort[++id] = now; //放入拓扑序中
for (int i = Head[now]; i; i = Nxt[i]) { //遍历其每一个子节点,入度-1
if (--in[To[i]] == 0) Q.push(To[i]); //假如子节点入度为0则入队
}
}
}
int main() {
cin >> n >> m;
int inp1, inp2;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &inp1, &inp2);
add_edge(inp1, inp2);
}
Top_sort();
for (int i = id; i > 0; i--) {
int to = topsort[i];
s[to][to] = 1; // 自己算一个
for (int j = Head[to]; j; j = Nxt[j]) { //异或所有子节点
s[to] |= s[To[j]];
}
}
for (int i = 1; i <= n; i++) printf("%d\n", s[i].count());
}
题目2: hdu4857 逃生
题目:http://acm.hdu.edu.cn/showproblem.php?pid=4857
这道题前面部分就是裸的拓扑排序问题,但是多了这个条件
负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推。
也就是说对求出来的拓扑序有要求,我们需要对求拓扑序的过程作修改。
再复习一下求拓扑序的过程
queue<int> Q;
id = 0;
for (int i = 1; i <= n; i++) if (in[i] == 0) Q.push(i); //假如入度为0则入队
while (!Q.empty()) {
int now = Q.front(); //每一个取出
Q.pop();
topsort[++id] = now; //放入拓扑序中
for (int i = Head[now]; i; i = Nxt[i]) { //遍历其每一个子节点,入度-1
if (--in[To[i]] == 0) Q.push(To[i]); //假如子节点入度为0则入队
}
}
对于
我们是搜索开头,小的优先入队,然后如此处理出来的结果就是 5 6 3 4 2 1
明显1需要尽可能排在前面,那么这样的结果就是错的.
最优解应该是6 4 1 5 3 2
那我用优先队列可不可以? 每次在可以选择的所有数中挑最小的放到拓扑序的数组中
还是对于这个例子:
最后求出5 3 2 6 4 1
很明显也不是啦,最后拓扑序对于权值越小的节点的优先级越高,因此开始就选择较小的链头有可能会错过最佳答案。
我们可以倒过来思考。
想一下,对于每个链尾,选择最大的那个数,将这个数在拓扑序中排在最后面,这样不会使最后的结果变坏。
所以我们取反链,每次选最大的数填进去,最后反过来输出拓扑序即可。
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 3e4 + 7;
vector<int> V[maxn];
int n, m, cnt;
int in[maxn];
int topsort[maxn];
void init() {
cnt = 0;
for (int i = 1; i <= n; i++) V[i].clear();
memset(in, 0, sizeof(in));
}
void Top_sort() {
priority_queue<int> Q; //使用优先队列,每次出队最大的
for (int i = 1; i <= n; i++) if (!in[i]) Q.push(i);
while (!Q.empty()) {
int now = Q.top();
Q.pop();
topsort[++cnt] = now; //以最大的构建拓扑序
for (int i = 0; i < V[now].size(); i++) {
int j = V[now][i];
if (--in[j] == 0) Q.push(j);
}
}
}
int main() {
int t; cin >> t;
while (t--) {
scanf("%d %d", &n, &m);
init();
int inp1, inp2;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &inp1, &inp2); //反过来建边
V[inp2].push_back(inp1);
in[inp1]++;
}
Top_sort();
for (int i = cnt; i > 1 ; i--) printf("%d ", topsort[i]); //最后逆序输出
printf("%d\n", topsort[1]);
}
return 0;
}