拓扑排序
拓扑排序
本人学艺不精,表述有待商讨!
相关资料
摘记
头次遇到图论的拓扑排序题,这次做一个整理。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;
}
}
例题
综合应用
-
topo判环,判唯一路径 洛谷 P1347 排序
-
dfs topo找所有从起点到终点的路径长度和条数 洛谷 P1685 游览
-
反向建有向图,topo找字典序最大的拓扑序列 洛谷 P3243 [HNOI2015] 菜肴制作
-
抽象建图拓扑排序求解问题 2023牛客多校第十场 - L
-
topo 求出到某个指定点为止的最小拓扑序列 ABC 315 E - Prerequisites
判断并输出唯一的拓扑排序序列
例题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;
}