|
|
|
|
## [$AcWing$ $343$. 排序](https://www.acwing.com/problem/content/345/)
|
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
给定 $n$ 个变量和 $m$ 个不等式。其中 $n$ 小于等于 $26$,变量分别用前 $n$ 的大写英文字母表示。
|
|
|
|
|
|
|
|
|
|
不等式之间具有传递性,即若 $A>B$ 且 $B>C$,则 $A>C$。
|
|
|
|
|
|
|
|
|
|
请从前往后遍历每对关系,每次遍历时判断:
|
|
|
|
|
|
|
|
|
|
* 如果能够确定全部关系且无矛盾,则结束循环,输出确定的次序;
|
|
|
|
|
* 如果发生矛盾,则结束循环,输出有矛盾;
|
|
|
|
|
* 如果循环结束时没有发生上述两种情况,则输出无定解。
|
|
|
|
|
|
|
|
|
|
**输入格式**
|
|
|
|
|
输入包含多组测试数据。
|
|
|
|
|
|
|
|
|
|
每组测试数据,第一行包含两个整数 $n$ 和 $m$。
|
|
|
|
|
|
|
|
|
|
接下来 $m$ 行,每行包含一个不等式,不等式全部为 **小于** 关系。
|
|
|
|
|
|
|
|
|
|
当输入一行 `0 0` 时,表示输入终止。
|
|
|
|
|
|
|
|
|
|
**输出格式**
|
|
|
|
|
每组数据输出一个占一行的结果。
|
|
|
|
|
|
|
|
|
|
结果可能为下列三种之一:
|
|
|
|
|
|
|
|
|
|
* 如果可以确定两两之间的关系,则输出 `Sorted sequence determined after t relations: yyy...y.`,其中`t`指 **迭代次数**,`yyy...y`是指 **升序排列** 的所有变量。
|
|
|
|
|
|
|
|
|
|
* 如果有矛盾,则输出: `Inconsistency found after t relations.`,其中`t`指迭代次数。
|
|
|
|
|
|
|
|
|
|
* 如果没有矛盾,且不能确定两两之间的关系,则输出 `Sorted sequence cannot be determined.`。
|
|
|
|
|
|
|
|
|
|
**数据范围**
|
|
|
|
|
$2≤n≤26$,变量只可能为大写字母 $A$∼$Z$。
|
|
|
|
|
|
|
|
|
|
**输入样例$1$**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
4 6
|
|
|
|
|
A<B
|
|
|
|
|
A<C
|
|
|
|
|
B<C
|
|
|
|
|
C<D
|
|
|
|
|
B<D
|
|
|
|
|
A<B
|
|
|
|
|
3 2
|
|
|
|
|
A<B
|
|
|
|
|
B<A
|
|
|
|
|
26 1
|
|
|
|
|
A<Z
|
|
|
|
|
0 0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输出样例$1$**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
Sorted sequence determined after 4 relations: ABCD.
|
|
|
|
|
Inconsistency found after 2 relations.
|
|
|
|
|
Sorted sequence cannot be determined.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输入样例$2$**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
6 6
|
|
|
|
|
A<F
|
|
|
|
|
B<D
|
|
|
|
|
C<E
|
|
|
|
|
F<D
|
|
|
|
|
D<E
|
|
|
|
|
E<F
|
|
|
|
|
0 0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输出样例$2$**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
Inconsistency found after 6 relations.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输入样例$3$**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
5 5
|
|
|
|
|
A<B
|
|
|
|
|
B<C
|
|
|
|
|
C<D
|
|
|
|
|
D<E
|
|
|
|
|
E<A
|
|
|
|
|
0 0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**输出样例$3$**:
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
Sorted sequence determined after 4 relations: ABCDE.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 二、$floyd$ 求传递闭包
|
|
|
|
|
|
|
|
|
|
**概念**
|
|
|
|
|
给定若干对元素和若干对二元关系,并且关系具有传递性,通过传递性推导出尽量多的元素之间关系的问题被称为 **传递闭包**。
|
|
|
|
|
|
|
|
|
|
>**解释**:比如$a < b,b < c$,就可以推导出$a < c$,如果用图形表示出这种大小关系,就是$a$到$b$有一条有向边,$b$到$c$有一条有向边,可以推出$a$可以到达$c$,找出图中各点能够到达点的集合,就 **类似** 于$floyd$算法求图中任意两点间的最短距离。
|
|
|
|
|
|
|
|
|
|
**模板**
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
//传递闭包
|
|
|
|
|
void floyd(){
|
|
|
|
|
for(int k = 0;k < n;k++)
|
|
|
|
|
for(int i = 0;i < n;i++)
|
|
|
|
|
for(int j = 0;j < n;j++)
|
|
|
|
|
f[i][j] |= f[i][k] & f[k][j];
|
|
|
|
|
}
|
|
|
|
|
// 原始版本
|
|
|
|
|
/*
|
|
|
|
|
for (int k = 0; k < n; k++)
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
for (int j = 0; j < n; j++)
|
|
|
|
|
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
|
|
|
|
|
*/
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**回到本题**
|
|
|
|
|
|
|
|
|
|
- 题目描述要求按顺序遍历二元关系,一旦前$i$个二元关系可以确定次序了就不再遍历了,即使第$i + 1$对二元关系就会出现矛盾也不去管它了。
|
|
|
|
|
- 题目字母只会在$A$到$Z$间,因此可以映射为$0$到$25$这$26$个元素
|
|
|
|
|
- $A < B$,则$f[0][1]=1$。如果$f[0][1] = f[1][0] = 1$,推出$f[0][0] = 1$,此时$A < B$并且$B < A$发生矛盾,即$f[i][i]= 1$时表示发生矛盾。
|
|
|
|
|
|
|
|
|
|
**算法步骤**
|
|
|
|
|
每读取一对二元关系,就执行一遍$floyd$算法求 **传递闭包**,然后执行$check$函数判断:
|
|
|
|
|
* ① 如果发生矛盾终止遍历
|
|
|
|
|
* ② 如果次序全部被确定终止遍历
|
|
|
|
|
* ③ 两者都没有,继续遍历
|
|
|
|
|
|
|
|
|
|
在确定所有的次序后,需要 **输出大小关系**,需要一个$getorder$函数。
|
|
|
|
|
|
|
|
|
|
>**注意**:
|
|
|
|
|
终止遍历仅仅是不再针对新增的二元关系去求传递闭包,循环还是要继续的,需要读完数据才能继续读下一组数据。
|
|
|
|
|
|
|
|
|
|
下面设计$check$函数和$getorder$函数。
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
// 1:可以确定两两之间的关系,2:矛盾,3:不能确定两两之间的关系
|
|
|
|
|
int check() {
|
|
|
|
|
// 如果i<i,那么就是出现了矛盾
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
if (f[i][i]) return 2;
|
|
|
|
|
// 存在还没有识别出关系的两个点i,j,还要继续读入
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
for (int j = 0; j < i; j++)
|
|
|
|
|
if (!f[i][j] && !f[j][i]) return 3;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
* ① $f[i][i] = 1$ 发生矛盾
|
|
|
|
|
* ② $f[i][j] = f[j][i] = 0$ 表示$i$与$j$之间的大小关系还没有确定下来,需要继续读取下一对二元关系
|
|
|
|
|
* ③ 所有的关系都确定,而且没有发生矛盾
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
string getorder(){
|
|
|
|
|
char s[26];
|
|
|
|
|
for(int i = 0;i < n;i++){
|
|
|
|
|
int cnt = 0;
|
|
|
|
|
for(int j = 0;j < n;j++) cnt += f[i][j];//有多少个数大于i
|
|
|
|
|
s[n - cnt - 1] = i + 'A'; //反着才能记录下名次
|
|
|
|
|
}
|
|
|
|
|
return string(s,s + n); //用char数组构造出string返回
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
> **解释**:确定所有元素次序后如何判断元素`i`在第几个位置呢?`f[i][j] = 1`表示`i < j`,因此计算下`i`小于元素的个数`cnt`,就可以判定`i`是第`cnt + 1`大的元素了
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### $Code$
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
// Floyd解决传送闭包问题
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 27;
|
|
|
|
|
int n; // n个变量
|
|
|
|
|
int m; // m个不等式
|
|
|
|
|
int f[N][N]; // 传递闭包结果
|
|
|
|
|
|
|
|
|
|
void floyd() {
|
|
|
|
|
for (int k = 0; k < n; k++)
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
for (int j = 0; j < n; j++)
|
|
|
|
|
f[i][j] |= f[i][k] & f[k][j]; // i可以到达k,k可以到达j,那么i可以到达j
|
|
|
|
|
}
|
|
|
|
|
// 1:可以确定两两之间的关系,2:矛盾,3:不能确定两两之间的关系
|
|
|
|
|
int check() {
|
|
|
|
|
// 如果i<i,那么就是出现了矛盾
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
if (f[i][i]) return 2;
|
|
|
|
|
// 存在还没有识别出关系的两个点i,j,还要继续读入
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
for (int j = 0; j < i; j++)
|
|
|
|
|
if (!f[i][j] && !f[j][i]) return 3;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string getorder() { // 升序输出所有变量
|
|
|
|
|
char s[26];
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
|
int cnt = 0;
|
|
|
|
|
// f[i][j] = 1表示i可以到达j (i< j)
|
|
|
|
|
for (int j = 0; j < n; j++) cnt += f[i][j]; // 比i大的有多少个
|
|
|
|
|
// 举个栗子:i=0,表示字符A
|
|
|
|
|
// 比如比i大的有5个,共6个字符:ABCDEF
|
|
|
|
|
// n - cnt - 1 = 6-5-1 = 0,也就是A放在第一个输出的位置上, 之所以再-1,是因为下标从0开始
|
|
|
|
|
s[n - cnt - 1] = i + 'A';
|
|
|
|
|
}
|
|
|
|
|
// 转s字符数组为字符串
|
|
|
|
|
return string(s, s + n);
|
|
|
|
|
}
|
|
|
|
|
int main() {
|
|
|
|
|
// n个变量,m个不等式
|
|
|
|
|
// 当输入一行 0 0 时,表示输入终止
|
|
|
|
|
while (scanf("%d %d", &n, &m), n && m) {
|
|
|
|
|
string S;
|
|
|
|
|
int k = 3; // 3:不能确定两两之间的关系
|
|
|
|
|
memset(f, 0, sizeof f); // 初始化邻接矩阵
|
|
|
|
|
// m条边
|
|
|
|
|
for (int i = 1; i <= m; i++) {
|
|
|
|
|
cin >> S;
|
|
|
|
|
// 已确定或者出现了矛盾,就没有必要再处理了,但是,还需要耐心的读取完毕,因为可能还有下一轮,不读入完耽误下一轮
|
|
|
|
|
if (k < 3) continue;
|
|
|
|
|
// 变量只可能为大写字母A~Z,映射到0~25
|
|
|
|
|
int a = S[0] - 'A', b = S[2] - 'A';
|
|
|
|
|
f[a][b] = 1; // 记录a<b
|
|
|
|
|
|
|
|
|
|
// 每输入一个关系,就计算一遍传递闭包
|
|
|
|
|
floyd();
|
|
|
|
|
|
|
|
|
|
// 检查一下现在的情况,是不是已经可以判定了
|
|
|
|
|
k = check();
|
|
|
|
|
|
|
|
|
|
if (k == 2) // 出现矛盾
|
|
|
|
|
printf("Inconsistency found after %d relations.\n", i);
|
|
|
|
|
else if (k == 1) { // 可以确定
|
|
|
|
|
string ans = getorder(); // 输出升序排列的所有变量
|
|
|
|
|
printf("Sorted sequence determined after %d relations: %s.\n", i, ans.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 所有表达式都输入了,仍然定不下来关系
|
|
|
|
|
if (k == 3) printf("Sorted sequence cannot be determined.\n");
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 三、拓扑序解法
|
|
|
|
|
开始想到拓扑排序,但是感觉从前往后枚举一遍会超时,然后看了蓝书$floyd$的做法实现了一遍,感觉还不如直接拓扑。
|
|
|
|
|
|
|
|
|
|
大致是:
|
|
|
|
|
|
|
|
|
|
$m$次循环,每次加入一条边到图中,再跑一遍拓扑排序
|
|
|
|
|
|
|
|
|
|
- 排序后,排序数组不为$n$个,则表示有环,矛盾,跳出循环
|
|
|
|
|
- 排序后,排序数组为$n$个,但是在过程中,有$2$个或以上的点在队列中,表示拓扑序并不唯一,那么此时并不能确定所有点的顺序,因此进行下一次循环
|
|
|
|
|
- 排序后,排序数组为$n$个,且在过程中,队列中一直只有一个,拓扑序唯一,输出结果,跳出循环
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
const int N = 30, M = N * N;
|
|
|
|
|
|
|
|
|
|
int n, m;
|
|
|
|
|
int a[1050], b[1050]; // a[i]<b[i]
|
|
|
|
|
char s[N]; // 输入的偏序关系
|
|
|
|
|
|
|
|
|
|
int in[N], ind[N];
|
|
|
|
|
int d[N], dl;
|
|
|
|
|
|
|
|
|
|
// 链式前向星
|
|
|
|
|
int e[M], h[N], idx, w[M], ne[M];
|
|
|
|
|
void add(int a, int b, int c = 0) {
|
|
|
|
|
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
拓扑序
|
|
|
|
|
(1) 出队列节点数量小于n,表示有环,矛盾
|
|
|
|
|
(2) 出队列节点数量等于n,在过程中,有2个或以上的点在队列中,表示拓扑序并不唯一,那么此时并不能确定所有点的顺序
|
|
|
|
|
(3) 出队列节点数量等于n,在过程中,队列中一直只有一个,拓扑序唯一
|
|
|
|
|
*/
|
|
|
|
|
int topsort() {
|
|
|
|
|
queue<int> q;
|
|
|
|
|
bool flag = 0;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < n; i++) // 枚举每个节点,入度为零的入队列
|
|
|
|
|
if (in[i] == 0) q.push(i);
|
|
|
|
|
|
|
|
|
|
while (q.size()) {
|
|
|
|
|
/*
|
|
|
|
|
注意:此处需要优先检查是不是有环,即使检查到某个点有多个前序节点,也并不表示它应该返回2,因为此时也可能是一个环!
|
|
|
|
|
因为一旦检查是环,就不必再录入新的大小关系的,是一个截止的标识!
|
|
|
|
|
总结:判断是不是拓扑序不唯一的标准是:
|
|
|
|
|
① 队列节点数量等于n
|
|
|
|
|
② 在过程中,有2个或以上的点在队列中
|
|
|
|
|
|
|
|
|
|
如果只发现了②就着急返回拓扑序不唯一,就可能会掉入到是环的坑中!
|
|
|
|
|
*/
|
|
|
|
|
if (q.size() > 1) flag = 1;
|
|
|
|
|
|
|
|
|
|
int u = q.front();
|
|
|
|
|
q.pop();
|
|
|
|
|
d[++dl] = u; // 按出队列的顺序来记录由小到大的关系
|
|
|
|
|
for (int i = h[u]; ~i; i = ne[i]) {
|
|
|
|
|
int j = e[i];
|
|
|
|
|
if (--in[j] == 0) q.push(j);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 有环
|
|
|
|
|
if (dl < n) return 1;
|
|
|
|
|
// 不确定
|
|
|
|
|
if (dl == n && flag) return 2;
|
|
|
|
|
// 已确定
|
|
|
|
|
return 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
// n个变量,m个不等式,也就是n 个节点,m条边
|
|
|
|
|
while (~scanf("%d%d", &n, &m) && n | m) {
|
|
|
|
|
// 多组测试数据,需要初始化
|
|
|
|
|
|
|
|
|
|
// 链式前向星
|
|
|
|
|
memset(h, -1, sizeof h);
|
|
|
|
|
idx = 0;
|
|
|
|
|
// 入度数组初始化
|
|
|
|
|
memset(ind, 0, sizeof ind);
|
|
|
|
|
|
|
|
|
|
// 输入大小关系,'A'->0,...,'Z'->25
|
|
|
|
|
for (int i = 1; i <= m; i++) {
|
|
|
|
|
scanf("%s", s); // 通用格式 类似于: B<C
|
|
|
|
|
a[i] = s[0] - 'A', b[i] = s[2] - 'A'; // 用两个数组a[],b[]记录关系,表示a[i]<b[i]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool flag = 1; // 是不是已经找出了全部的大于关系序列
|
|
|
|
|
|
|
|
|
|
// 逐个讨论每个大小关系
|
|
|
|
|
for (int i = 1; i <= m; i++) {
|
|
|
|
|
dl = 0; // 拓扑序输出数组清零
|
|
|
|
|
add(a[i], b[i]); // 建图
|
|
|
|
|
ind[b[i]]++; // 记录b[i]入度
|
|
|
|
|
|
|
|
|
|
// 因为topsort会在过程中执行--ind[j],而此图和入度的值后面还要继续用,不能让topsort改坏了
|
|
|
|
|
// 复制出来一个临时的入度数组in[]
|
|
|
|
|
memcpy(in, ind, sizeof ind);
|
|
|
|
|
|
|
|
|
|
// 每输入一个关系表达式,就topsort一次
|
|
|
|
|
int res = topsort();
|
|
|
|
|
|
|
|
|
|
// 拓扑序唯一
|
|
|
|
|
if (res == 3) {
|
|
|
|
|
printf("Sorted sequence determined after %d relations: ", i);
|
|
|
|
|
for (int j = 1; j <= dl; j++) printf("%c", d[j] + 'A');
|
|
|
|
|
puts(".");
|
|
|
|
|
flag = 0;
|
|
|
|
|
break;
|
|
|
|
|
} else if (res == 1) { // 有环
|
|
|
|
|
printf("Inconsistency found after %d relations.\n", i);
|
|
|
|
|
flag = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 最终还是没有发现矛盾,也没有输出唯一序,说明条件还是不够啊,顺序无法确定
|
|
|
|
|
if (flag) puts("Sorted sequence cannot be determined.");
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> **注**:此题没有给出$M$的数据范围,导致我调试了半个多小时,差评$yxc$
|