拓扑排序 学习日记 + 题目(ch2101 && hdu4857)

拓扑排序:

给定一张有向无环图,若由途中节点组成的序列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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值