「周记」拓扑排序

欢迎访问 \(cjwen's\ blog\)

拓扑排序的英文名是 Topological sorting。
拓扑排序要解决的问题是给一个图的所有节点排序。
拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点。 ——OI-WiKi

拓扑排序是在 DAG(有向无环图) 中每次选则入度为 0 的节点加入队列,并删除与这个节点相连的边,重复执行此操作。
这样做的作用是后面加入队列的节点一定不依赖于前面的节点,因此拓扑排序有无后效性,可以用于 dp
也可以用于判环,当队列为空时若还有边则该图有环。

例题 1:

P1807 最长路

思路很简单,d[] 用于存储最长距离,rd[] 用于存储入度。

第一遍代码:

#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;

const int N = 1505;

int n, m;
int u, v, w;
int t;
long long d[N];
bool f[N][N];
int g[N][N];
int rd[N];

queue <int> q;

int main(){
	
	scanf("%d%d", &n, &m);
	
	for(int i = 1; i <= n; i++){
		d[i] = -0x7f7f7f7f7f7f7f7f;
	}
	
	while(m--){
		scanf("%d%d%d", &u, &v, &w);
		rd[v]++;
		f[u][v] = 1;
		g[u][v] = w;
	}
	
	d[1] = 0;
	q.push(1);
	
	while(!q.empty()){
		t = q.front();
		q.pop();
		for(int i = 1; i <= n; i++){
			if(f[t][i]){
				d[i] = max(d[i], d[t] + g[t][i]);
				rd[i]--;
				if(rd[i] == 0){
					q.push(i);
				}
			}
		}
	}
	
	if(d[n] == -0x7f7f7f7f7f7f7f7f){
		puts("-1");
	}else{
		printf("%lld\n", d[n]);
	}
	
	return 0;
}

45 分,下了一组数据,发现有重边的情况。。。。
略改输入部分代码。

#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;

const int N = 1505;

int n, m;
int u, v, w;
int t;
long long d[N];
bool f[N][N];
int g[N][N];
int rd[N];

queue <int> q;

int main(){
	
	scanf("%d%d", &n, &m);
	
	for(int i = 1; i <= n; i++){
		d[i] = -0x7f7f7f7f7f7f7f7f;
	}
	
	while(m--){
		scanf("%d%d%d", &u, &v, &w);
		if(!f[u][v]) rd[v]++;
		f[u][v] = 1;
		g[u][v] = max(g[u][v], w);
	}
	
	d[1] = 0;
	q.push(1);
	
	while(!q.empty()){
		t = q.front();
		q.pop();
		for(int i = 1; i <= n; i++){
			if(f[t][i]){
				d[i] = max(d[i], d[t] + g[t][i]);
				rd[i]--;
				if(rd[i] == 0){
					q.push(i);
				}
			}
		}
	}
	
	if(d[n] == -0x7f7f7f7f7f7f7f7f){
		puts("-1");
	}else{
		printf("%lld\n", d[n]);
	}
	
	return 0;
}

然并卵,还是 WA。
原因是其他与目标路径无关的点没有入队,导致部分点最长路已经确定了,但入度不为 0,解决方法就是在 1 入队之前先把其他入度为 0 的点入队,A 之。

最终代码:

#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;

const int N = 1505;

int n, m;
int u, v, w;
int t;
long long d[N];
bool f[N][N];
int g[N][N];
int rd[N];

queue <int> q;

int main(){
	
	scanf("%d%d", &n, &m);
	
	for(int i = 1; i <= n; i++){
		d[i] = -0x7f7f7f7f7f7f7f7f;
	}
	
	while(m--){
		scanf("%d%d%d", &u, &v, &w);
		if(!f[u][v]) rd[v]++;
		f[u][v] = 1;
		g[u][v] = max(g[u][v], w);
	}
	
	for(int i = 2; i <= n; i++){
		if(rd[i] == 0){
			q.push(i);
		}
	}
	
	while(!q.empty()){
		t = q.front();
		q.pop();
		for(int i = 1; i <= n; i++){
			if(f[t][i]){
				rd[i]--;
				if(rd[i] == 0){
					q.push(i);
				}
			}
		}
	}
	
	d[1] = 0;
	q.push(1);
	
	while(!q.empty()){
		t = q.front();
		q.pop();
		for(int i = 1; i <= n; i++){
			if(f[t][i]){
				d[i] = max(d[i], d[t] + g[t][i]);
				rd[i]--;
				if(rd[i] == 0){
					q.push(i);
				}
			}
		}
	}
	
	if(d[n] == -0x7f7f7f7f7f7f7f7f){
		puts("-1");
	}else{
		printf("%lld\n", d[n]);
	}
	
	return 0;
}

其实可以跑 SPAF

例题 2:

P1137 旅行计划

类似板子题,一开始只需要将所有入度为 0 的点入队,没有奇怪数据。

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>

using namespace std;

const int N = 1e5 + 10;

int n, m, x, y, t;
int rd[N], d[N];

vector <int> g[N];
queue <int> q;

int main(){
	
	scanf("%d%d", &n, &m);
	
	for(int i = 1; i <= n; i++){
		d[i] = -0x7fffffff;
	}
	
	while(m--){
		scanf("%d%d", &x, &y);
		rd[y]++;
		g[x].push_back(y);
	}
	
	for(int i = 1; i <= n; i++){
		if(rd[i] == 0){
			q.push(i);
			d[i] = 1;
		}
	}
	
	while(!q.empty()){
		t = q.front();
		q.pop();
		for(int i = 0; i < g[t].size(); i++){
			d[g[t][i]] = max(d[g[t][i]], d[t] + 1);
			rd[g[t][i]]--;
			if(rd[g[t][i]] == 0){
				q.push(g[t][i]);
			}
		}
	}
	
	for(int i = 1; i <= n; i++){
		printf("%d\n", d[i]);
	}
	
	return 0;
	
}

总结:

  1. 要注意题目对于图的描述,是否有重边或自环;
  2. 注意数据范围,最长的距离是等于 \((n-1) \cdot d_{max}\);
  3. 我可真是蒟蒻,连拓扑排序都写不清楚。
上一篇:带你读《5G 无线增强设计与国际标准》第三章增强多天线技术3.1增强信息状态信息反馈(二)


下一篇:蓝桥杯【真题演练】完全二叉树的权值