拓扑排序

拓扑排序

本人学艺不精,表述有待商讨!

相关资料

oi-wiki拓扑排序

摘记

  头次遇到图论的拓扑排序题,这次做一个整理。oi-wiki上的内容非常详细,此处不做搬运。
  有向图的拓扑排序是对其顶点的一种线性排序,使得对于从顶点u到顶点v的每个有向边uv在排序中u都在v之前。当且仅当图中没有定向环时(即有向无环图),才有可能进行拓扑排序。
  基于这样子的一种特性,我们可以轻松的获得有向无环图两顶点之间的指向关系,并且对一些问题进行求解。

C++模板——来自oi-wiki

bool toposort(){
	vector<int> L;
	queue<int> S;
	for (int i = 1; i <= n; i++)
		if (in[i] == 0) S.push(i);
	while(!S.empty()){
		int u = S.front();
		S.pop();
		L.push_back(u);
		for (auto v : G[u]) {
			if (--in[v] == 0) {
				S.push(v);
			}
		}
	}
	if(L.size() == n){
		for (auto i : L) cout << i << ' ';
		return true;
	}else{
		return false;
	}
}

例题

综合应用

判断并输出唯一的拓扑排序序列

abc291_E Find Permutation

  例题1:我们想要通过题目给出的条件,找到唯一的具有大小关系的一个序列,那么这样的有向无环图一定是具有哈密顿通路的图。我们利用拓扑排序,找到从前到后的节点,维持入度为0的集合,当这个集合的元素个数大于1时,说明我们目前所判断的图存在多种解,不满足我们的条件,反之满足,并且得到的顶点顺序还满足代表元素从大到小的关系。

过题代码:

//>>>Qiansui
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
//#define int long long

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

using namespace std;
const int maxm=2e5+5,inf=0x3f3f3f3f;
int n,m,in[maxm],ans[maxm];
vector<int> G[maxm];//G为邻接数组
vector<int> L;//保存拓扑排序的结果
queue<int> S;//存储入度为零的点

bool toposolt(){
	int u;
	for(int i=1;i<=n;++i){
		if(in[i]==0) S.push(i);
	}
	if(S.size()>1) return false;
	while(!S.empty()){
		u=S.front();
		S.pop();
		L.push_back(u);
		for(auto v: G[u]){
			if(--in[v]==0) S.push(v);
		}
		if(S.size()>1) return false;//此题因为判断的是唯一存在性,所以>1就说明存在多种可能而不符合题意
	}
	return true;
}

void solve(){
	cin>>n>>m;
	int x,y;
	for(int i=0;i<m;++i){
		cin>>x>>y;//Ax<Ay
		auto it=find(G[y].begin(),G[y].end(),x);//为了去重
		if(it==G[y].end()){
			G[y].push_back(x);
			++in[x];
		}
	}
	if(toposolt()){
		cout<<"Yes\n";
		for(int i=0;i<n;++i){
			ans[L[i]-1]=n-i;//将结果表示在ans数组中
		}
		for(int i=0;i<n-1;++i){
			cout<<ans[i]<<" ";
		}
		cout<<ans[n-1]<<endl;
	}else cout<<"No\n";
	return ;
}

signed main(){
//	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
//	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

拓扑排序安排任务时序实现总时间最小

简单说说思路,利用拓扑排序的性质,我们可以找到先做和后做的事情,还可以找到可以同时做的事情。那么怎样才是我们想求的最大时间呢?我们可以知道,必须后挤的奶的挤奶开始时间其实是取决于先挤的奶的最后时间,所以我们可以将前面的最迟时间加到后面来(因为可能的情况之下,可以有多头牛排在前面?),以此类推,最终的子节点所得的时间就是这么一条挤奶路线的最大时间。所以我们可以将挤奶的牛划分为许多的有向图,统计计算每个有向图的最大时间之和即为最终的答案。

过题代码:

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define int long long

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

using namespace std;
const int maxm=1e5+5,inf=0x3f3f3f3f;
int n,m,t[maxm],in[maxm],cost[maxm];
vector<int> g[maxm];
queue<int> q;

void toposolt(){
	ll ans=0,c;
	while(!q.empty()){
		c=q.front();
		q.pop();
		for(auto x : g[c]){
			cost[x]=max(cost[x],cost[c]+t[x]);
			--in[x];
			if(in[x]==0){
				q.push(x);
			}
		}
	}
	for(int i=1;i<=n;++i) ans=max(ans,cost[i]);
	cout<<ans<<"\n";
	return ;
}

void solve(){
	int a,b;
	cin>>n>>m;
	for(int i=0;i<n;++i){
		cin>>t[i+1];
		cost[i+1]=t[i+1];
	}
	for(int i=0;i<m;++i){//a > b
		cin>>a>>b;
		auto it=find(g[a].begin(),g[a].end(),b);
		if(it==g[a].end()){
			g[a].push_back(b);
			++in[b];
		}
	}
	for(int i=1;i<=n;++i){
		if(in[i]==0){
			q.push(i);
		}
	}
	toposolt();
	return ;
}

signed main(){
//	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
//	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

===

简单题,利用拓扑排序找先后顺序再计算时间即可
Qiansui_code


判断整体通路数

洛谷 P4017 最大食物链计数
此题也是基本的拓扑排序的应用,就是多了点思维的东西。最主要的就是想,怎么数呢?怎么数从入度为0的点到出度为0的点的通路数呢?我想的是逐层向下,上层的父节点的路径条数加给所有的下层,之后清空上层;下层的路径以此类推,但当抵达最下层时,不清空当前层。再遍历求和取余得所有路径数。
过题代码:

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int N = 5e3 + 5, inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f, mod = 80112002;
int n, m, in[N], dp[N];
vector<int> e[N];

void solve(){
	cin >> n >> m;
	for(int i = 0; i < m; ++ i){
		int u, v;
		cin >> u >> v;
		e[v].push_back(u);
		++ in[u];
	}
	queue<int> q;
	for(int i = 1; i <= n; ++ i){
		if(in[i] == 0){
			q.push(i);
			dp[i] = 1;
		}
	}
	ll ans = 0;
	while(q.size()){
		int u = q.front();
		q.pop();
		for(auto v : e[u]){
			dp[v] = (dp[v] + dp[u]) % mod;
			-- in[v];
			if(in[v] == 0) q.push(v);
		}
		if(e[u].size() == 0){
			ans = (ans + dp[u]) % mod;
		}
	}
	cout << ans << '\n';
	return ;
}

signed main(){
	// freopen("in.txt", "r", stdin);
	// freopen("out.txt", "w", stdout);
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	// cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}

输出所有的可能的拓扑序列,按照字典序输出

百炼oj 1270:Following Orders
题意
给定所有的字母,再给定字母之间的关系,让你给出所有的按照字典序排列的拓扑序列

思路
将题目所给关系抽象成图,即可利用 dfs 的拓扑排序求解该问题
因为得输出所有的序列,所以还有一步清零恢复的操作,很有dfs的感觉

相关资料:传送门

代码

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int maxm = 30 + 10, maxn = 25 + 10, inf = 0x3f3f3f3f, mod = 998244353;
int n, a[maxn], dir[maxm][maxm];
int topo[maxn], vis[maxm], in[maxm];

void dfs(int z, int cnt){
	topo[cnt] = z;                  //记录拓扑序列
	if(cnt == n -1){                //could output
		for(int i = 0; i < n; ++ i)
			cout << (char)(topo[i] + 'a');
		cout << '\n';
		return ;
	}
	vis[z] = 1;
	for(int i = 0; i < n; ++ i){
		if(!vis[a[i]] && dir[z][a[i]]){
			--in[a[i]];
		}
	}
	for(int i = 0; i < n ; ++ i){
		if(!in[a[i]] && !vis[a[i]]){
			dfs(a[i], cnt + 1);
		}
	}
	for(int i = 0; i < n; ++ i){            //清空
		if(!vis[a[i]] && dir[z][a[i]]){
			++ in[a[i]];
		}
	}
	vis[z] = 0;
	return ;
}

void solve(){
	char s[100];
	int len;
	while(gets(s) != NULL){
		mem(dir, 0);
		mem(vis, 0);
		mem(in, 0);
		len = strlen(s);
		n = 0;
		for(int i = 0; i < len; ++ i){
			if(s[i] >= 'a' && s[i] <= 'z'){
				a[n ++] = s[i] - 'a';
			}
		}
		sort(a, a + n);             //为了按字典序输出
		gets(s);
		len = strlen(s);
		int first = 1;
		for(int i = 0; i < len; ++ i){
			int st, ed;
			if(first && s[i] >= 'a' && s[i] <= 'z'){
				first = 0;
				st = s[i] - 'a';    //起点
				continue;
			}
			if(!first && s[i] >= 'a' && s[i] <= 'z'){
				first = 1;
				ed = s[i] - 'a';    //终点
				dir[st][ed] = 1;    //记录先后关系
				++ in[ed];          //入度 + 1
				continue;
			}
		}
		for(int i = 0; i < n; ++ i){
			if(!in[a[i]]){
				dfs(a[i], 0);
			}
		}
		cout << '\n';
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	// cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}

posted on 2023-02-27 22:28  Qiansui  阅读(48)  评论(0编辑  收藏  举报