1,定义
拓扑排序用于解决点之间的排序问题,如a点必须排在b点前面,c点必须排在d点后面,拓扑排序通过对这些关系建边来实现排序
2,思想
用度数表示一个点前面还有几个点排完序才轮到到,只有自己度数为0时才可以轮到他排序。
会注意到,当存在一个环时,这里面的点是不可能度数为0的,所以我们也可以利用这个来判断是否存在环
3,例题一,逃生
思路:
1,我们思考,要求序号小在前面,也就不一定是字典序最小。
eg:有边1->10,in[10]=0,in[2]=0,in[1]=1(即1需要10排完序才输出)。
如果按照字典序最小,排序是2,10,1
如果按照题目,排序是10,1,2(这样1更靠前)
2,正难则反,我们考虑,如果建立反向边,每次都把入度为0(in[i]==0)的点中序号最大的拿出来,那么较小的序号不久尽量靠前了吗
eg:还是上面那个,in[10]=1(即1),in[2]=0,in[1]=0
输出就是0,1,10(反过来输出就答案)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f; //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 1e5 + 10;
int head[N], in[N], h[N], num;
struct node
{
int next, to;
} edge[N];
void add(int u, int v)
{
edge[++num].next = head[u];
edge[num].to = v;
head[u] = num;
}
int main()
{
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m, t, a, b;
cin >> t;
while (t--)
{
memset(head, 0, sizeof(head));
num = 0;
memset(in, 0, sizeof(in));
int k = 0;
cin >> n >> m;
for (int i = 1; i <= m; ++i)
{
cin >> a >> b;
add(b, a);
in[a]++;
}
priority_queue<int>q;//优先队列把序号大的序号为0的先输出
for (int i = 1; i <= n; ++i)if (!in[i])q.push(i);
while (!q.empty())
{
int u = q.top();
q.pop();
h[++k] = u;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to;
if (--in[v] == 0)q.push(v);//入度为0就可以存入
}
}
for (int i = k; i >= 1; --i)//反向输出
{
cout << h[i];
if (i == 1)cout << endl;
else cout << ' ';
}
}
return 0;
}
例题二:Rank of Tetris
思路:
1,我们可以发现就是简单的跑个入度即可
2,当然,=号有些难操作,我们思考他的作用,假设a=b(a序号<b),而信息给了m1个严格大于a的数,又给了m2个严格小于b的数,实际隐含有m1个严格大于b,还有m2个严格小于a这样的信息,即我们应该把ab合并一起。合并使用并查集
3,我们考虑合并的影响,合并后我们需要注意是否这个“点”会形成自环(矛盾)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f; //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 2e4 + 10;
int head[N], in[N], num, a[N], b[N], n, m, fa[N], flag, ans;
char s[N];
struct node
{
int next, to;
} edge[N];
void add(int u, int v)
{
edge[++num].next = head[u];
edge[num].to = v;
head[u] = num;
}
void init()
{
memset(head, 0, sizeof(head));
memset(in, 0, sizeof(in));
for (int i = 0; i < n; ++i)fa[i] = i;//初始父亲为自己
num = 0;
flag = 1;//一开始flag为可以,后面0则不确定,-1则不行
ans = n;//ans表示合并后剩余点的数量
}
int find(int x)
{
if (fa[x] != x)fa[x] = find(fa[x]);
return fa[x];
}
void mjudge()
{
queue<int>q;
int cnt = 0;
for (int i = 0; i < n; ++i)if (!in[i] && i == fa[i])q.push(i);//首先该点入度为0而且还要是自己就是祖先才存入(所有剩余的点都是祖先,不是祖先的说明被合并了)
while (!q.empty())
{
if ((int)q.size() > 1)flag = 0;//如果同时入度02个以上,说明不明确输出状况了,flag为0,但是不能直接返回,因为矛盾优先度高,必须跑完才知道是否矛盾
int u = q.front();
q.pop();
cnt++;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to;
if (v == u)
{
flag = -1;//矛盾直接返回
return;
}
if (--in[v] == 0)q.push(v);
}
}
if (cnt < ans)flag = -1;//最后不是所有剩余点都入度,那么存在环
}
int main()
{
int x, y;
while (cin >> n >> m)
{
init();//该处初始化
for (int i = 1; i <= m; ++i)
{
cin >> a[i] >> s[i] >> b[i];
if (s[i] == '=')//先合并操作
{
x = find(a[i]), y = find(b[i]);
if (x != y)fa[x] = y, ans--;//即使数据给重复,我们也不会重复合并,每一次合并都会减少一个点,即使是两个合并点合并成一个点也一样,每次合并,剩余点ans减一
}
}
for (int i = 1; i <= m; ++i)if (s[i] != '=')//再对合并完的点操作
{
x = find(a[i]), y = find(b[i]);
if (s[i] == '>')add(x, y), in[y]++;
else add(y, x), in[x]++;
}
mjudge();
if (flag == 1)cout << "OK" << endl;
else if (!flag)cout << "UNCERTAIN" << endl;
else cout << "CONFLICT" << endl;
}
return 0;
}