拓扑排序的适用范围:有向无环图(DAG)
实际上拓扑排序不止可以用来求拓扑序
在
D
A
G
DAG
DAG 中,我们即可以用
t
o
p
s
o
r
t
topsort
topsort 求最长路也可以求最短路
模板
拓扑排序板子:
以下是一个
n
+
m
n+m
n+m 的实现(
n
,
m
n,m
n,m 分别表示点数和边数),利用了队列:
// deg是入度,在存图的时候需要录入数据
// A是排序后的数组
int deg[MAXN], A[MAXN];
bool toposort(int n)
{
int cnt = 0;
queue<int> q;
for (int i = 1; i <= n; ++i)
if (deg[i] == 0)
q.push(i);
while (!q.empty())
{
int t = q.front();
q.pop();
A[cnt++] = t;
for (auto to : edges[t])
{
deg[to]--;
if (deg[to] == 0) // 出现了新的入度为0的点
q.push(to);
}
}
return cnt == n;
}
返回值为是否成功进行拓扑排序,也即是否存在环。也就是说拓扑排序是可以用来简单地判环的。有时会要求输出字典序最小的方案,这时把 q u e u e queue queue 改成 p r i o r i t y priority priority_ q u e u e queue queue即可,复杂度会多一个 l o g log log。
在判断无向图中是否存在环时,是将所有 度 <= 1 的结点入队;
在判断有向图中是否存在环时,是将所有 入度 = 0 的结点入队。
C. Fox And Names
一道例题
思路:
对决定相邻字符串顺序的字符建图
拓扑排序
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define mem(x, d) memset(x, d, sizeof(x))
#define eps 1e-6
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
string s[maxn];
int in[maxn];
int ans[maxn], cnt;
vector <int> e[maxn];
bool topsort(){
queue <int> q;
for(int i = 0; i < 26; ++i) if(!in[i]) q.push(i);
while(!q.empty()){
int x = q.front();q.pop();
ans[++cnt] = x;
for(int i = 0; i < e[x].size(); ++i){
int to = e[x][i];
in[to]--;
if(!in[to]) q.push(to);
}
}
return cnt == 26;
}
// adefghijklmnopqrstuvwxyzbc
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> s[i];
bool f = 1;
for(int i = 1; i < n; ++i){
int j = 0;
while(s[i][j] == s[i+1][j]) ++j;
if(j < s[i].size() && j < s[i+1].size()){
int x = s[i][j] - 'a', y = s[i+1][j] - 'a';
in[y]++;
e[x].push_back(y);
}
else if(s[i].size() > s[i+1].size()) f = 0;
}
if(!f || !topsort()) cout << "Impossible";
else {
for(int i = 1; i <= 26; ++i) cout << (char)(ans[i] + 'a');
}
}
int main()
{
// ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
最简单的拓扑排序 入门
ZCMU-2153
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<set>
using namespace std;
const int mod = 1e6 + 10;
const int N = 3e7+10;
const int inf = 0x7fffffff;
int n, m;
int in[50];
vector <int> e[50], ans;
int main()
{
set<int> k;
char s[5];
while(~scanf("%s", s))
{
int x = s[0]-'A', y = s[2]-'A';
k.insert(x);k.insert(y);
if(s[1]=='>')
{
in[y]++;
e[x].push_back(y);
}
else
{
in[x]++;
e[y].push_back(x);
}
}
priority_queue<int, vector<int> , greater<int> > q;
// 因为要输出字典序最小的答案,所以用优先队列维护
for(int i = 0; i <= 30; ++i)
{
if(!in[i] && k.count(i)) q.push(i);
}
while(!q.empty())
{
int now = q.top();
q.pop();
ans.push_back(now);
for(int i = 0; i < e[now].size(); ++i)
{
int tmp = e[now][i];
in[tmp]--;
if(!in[tmp]) q.push(tmp);
}
}
if(ans.size() == k.size())
{
for(int i = 0; i < ans.size(); ++i)
printf("%c",ans[i] + 'A');
cout << endl;
}
else cout << "No Answer!\n";
return 0;
}
hdu4857
难度+++
题解
小的头部不一定排在前面,但是大的尾部一定排在后面
所以我们逆向思考,按照编号大,后出队(入度大的)的的在前的入队,最后逆序输出。
反向拓扑排序+优先队列
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<set>
using namespace std;
const int mod = 1e6 + 10;
const int N = 1e5 + 10;
const int inf = 0x7fffffff;
int n, m;
int in[N];
vector <int> e[N], ans;
void intt()
{
for(int i = 0; i <= n; ++i)
{
e[i].clear();in[i] = 0;
}
ans.clear();
}
void work()
{
cin >> n >> m;
int a, b;
intt();
for(int i = 1; i <= m; ++i)
{
scanf("%d %d", &a, &b);
in[a]++;
e[b].push_back(a);
}
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();
ans.push_back(now);
for(int i = 0; i < e[now].size(); ++i)
{
int tmp = e[now][i];
in[tmp]--;
if(!in[tmp]) q.push(tmp);
}
}
for(int i = ans.size() - 1; i >= 0; --i)
printf("%d%c",ans[i], i == 0 ? '\n' : ' ');
}
int main()
{
int t;
cin >> t;
while(t--)
work();
return 0;
}
/*
1
9 6
6 4
4 1
3 9
9 2
5 7
7 8
*/
洛谷1137
拓扑排序 + dp
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 9;
int n, m;
vector <int> v[N];
int dis[N], in[N];
queue <int> q;// 因为不需要用队列维护什么顺序,所以普通队列即可
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; ++i)
{
int a, b;
scanf("%d %d", &a, &b);
in[b]++;
v[a].push_back(b);
}
for(int i = 1; i <= n; ++i)
{
if(!in[i]) q.push(i);
dis[i] = 1;
}
while(!q.empty())
{
int now = q.front();
q.pop();
for(int i = 0 ; i < v[now].size(); ++i)
{
int tmp = v[now][i];
in[tmp]--;
if(!in[tmp])
{
q.push(tmp);
dis[tmp] = dis[now] + 1;
}
}
}
for(int i = 1; i <= n; ++i)
cout << dis[i] << endl;
return 0;
}
Stack-2021牛客暑假第二场
根据数组b中的值,构造序列a中数的大小关系的合法拓扑序
入度可能不唯一,但是出度唯一
#include<bits/stdc++.h>
#define ll long long
#define N 1000100
using namespace std;
int n, m, v[N], b[N];
int deg[N];// 入度
int to[N];// t[i] 为 i的后继节点
int st[N];// 栈数组
queue<int> q;
void bfs()
{
for (int i = 1; i <= n; ++i)
if (!deg[i]) q.push(i);// 入度为0进队列
int w = n;
while(!q.empty())
{
int now = q.front();
q.pop();
//cout << now << endl;
v[now] = w--;// 按照拓扑序从n开始依次赋值
if (!--deg[to[now]])
{
//cout << now << " " << to[now] << endl;
q.push(to[now]);
}
}
return;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i)
{
int x;scanf("%d", &x);
scanf("%d", &b[x]);
}
int res = 0;// 栈顶指针
for (int i = 1; i <= n; ++i)
{
if (b[i])
{
if (b[i] > res + 1) return puts("-1"), 0;
// 当前是res个,最多再加一个,大于这个就不可能
if(b[i] < res + 1)// 多了
{
while(b[i] < res + 1) res--;
to[st[res + 1]] = i; // 更改最后一个pop掉元素的指向, 指向 i
}
}
st[++res] = i; // i入栈 当前i是栈顶
to[st[res]] = st[res - 1];// i比栈顶大
// 由大数指向小数
}
for (int i = 1; i <= n; ++i) deg[to[i]]++;// 入度
//for(int i = 1; i <= n; ++i) cout << deg[i] << " ";cout << endl;
//for(int i = 1; i <= n; ++i) cout << to[i] << " ";cout << endl;
bfs();//跑拓扑序
for (int i = 1; i <= n; ++i)
printf("%d%c", v[i], i==n?'\n':' ');
return 0;
}
/*
7 3
3 3
5 2
7 2
*/