340 lines
11 KiB
Markdown
340 lines
11 KiB
Markdown
## [$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$有一条有向边 <font color='red' size=4><b>【小的向大的连一条边】</b></font>,$b$到$c$有一条有向边,可以推出$a$可以到达$c$,找出图中各点能够到达点的集合,**类似** 于$floyd$算法求图中任意两点间的最短距离 <font color='red' size=4><b>【魔改版的$floyd$】</b></font>。
|
||
|
||
**模板**
|
||
```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$ $O(N^3)$
|
||
```cpp {.line-numbers}
|
||
#include <bits/stdc++.h>
|
||
using namespace std;
|
||
|
||
const int N = 26;
|
||
|
||
int n, m;
|
||
int g[N][N];
|
||
bool st[N];
|
||
|
||
// 求传递闭包
|
||
void floyd() {
|
||
for (int k = 0; k < n; k++)
|
||
for (int i = 0; i < n; i++)
|
||
for (int j = 0; j < n; j++)
|
||
g[i][j] |= g[i][k] && g[k][j];
|
||
}
|
||
|
||
int check() {
|
||
for (int i = 0; i < n; i++)
|
||
if (g[i][i]) return 2; // 矛盾
|
||
|
||
for (int i = 0; i < n; i++)
|
||
for (int j = 0; j < i; j++)
|
||
if (!g[i][j] && !g[j][i]) // 待继续
|
||
return 0;
|
||
|
||
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 += g[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字符数组为字符串
|
||
string res;
|
||
for (int i = 0; i < n; i++) res = res + s[i];
|
||
return res;
|
||
}
|
||
|
||
int main() {
|
||
while (cin >> n >> m, n || m) {
|
||
memset(g, 0, sizeof g); // 邻接矩阵
|
||
int type = 0, t; // type: 0=还需要继续给出条件 1=找到了顺序 2=存在冲突
|
||
// t:在第几次输入后找到了顺序,不能中间break,因为那样会造成数据无法完成读入,后续的操作无法进行,只能记录下来当时的i
|
||
for (int i = 1; i <= m; i++) {
|
||
char s[5];
|
||
cin >> s;
|
||
int a = s[0] - 'A', b = s[2] - 'A'; // A->0,B->1,...,Z->25完成映射关系
|
||
|
||
if (!type) { // 如果不存在矛盾,就尝试找出大小的顺序
|
||
g[a][b] = 1; // 有边
|
||
floyd(); // 求传递闭包
|
||
type = check(); // 检查是不是存在矛盾,或者找到了完整的顺序
|
||
if (type > 0) t = i; // 如果找到了顺序,或者发现了矛盾,记录是第几次输入后发现的
|
||
}
|
||
// 即使存在矛盾,也需要继续读入,直到本轮数据读入完成
|
||
}
|
||
|
||
if (!type)
|
||
puts("Sorted sequence cannot be determined.");
|
||
else if (type == 2)
|
||
printf("Inconsistency found after %d relations.\n", t);
|
||
else {
|
||
string ans = getorder(); // 输出升序排列的所有变量
|
||
printf("Sorted sequence determined after %d relations: %s.\n", t, ans.c_str());
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### 三、优化版本
|
||
$O(N^2)$
|
||
|
||
其实,由于每次新增加的一对$(a,b)$,只会更新与$a,b$有边连接的点,其它的无关点是没有影响的,如果加上一对$(a,b)$就去全新计算,无疑是存在浪费的,可以优化的。
|
||
|
||
怎么优化呢?核心思路就是$(a,b)$做为$floyd$算法的中继点即可,其它点不再被遍历做为中继点。
|
||
|
||
说人话就是:
|
||
① 遍历所有节点,找出所有小于$a$的节点$x$,那么$x$一定小于$b$。
|
||
② 遍历所有节点,找出所有大于$b$的节点$x$,那么$a$一定小于$x$。
|
||
③ 遍历所有节点,如果$x<a$,并且,$b<y$,那么$x<y$
|
||
|
||

|
||
|
||
```cpp {.line-numbers}
|
||
#include <bits/stdc++.h>
|
||
using namespace std;
|
||
|
||
const int N = 26;
|
||
|
||
int n, m;
|
||
bool g[N][N];
|
||
bool st[N];
|
||
|
||
int check() {
|
||
for (int i = 0; i < n; i++)
|
||
if (g[i][i]) return 2; // 矛盾
|
||
|
||
for (int i = 0; i < n; i++)
|
||
for (int j = 0; j < i; j++)
|
||
if (!g[i][j] && !g[j][i]) // 待继续
|
||
return 0;
|
||
|
||
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 += g[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字符数组为字符串
|
||
string res;
|
||
for (int i = 0; i < n; i++) res = res + s[i];
|
||
return res;
|
||
}
|
||
|
||
int main() {
|
||
while (cin >> n >> m, n || m) {
|
||
memset(g, 0, sizeof g);
|
||
|
||
int type = 0, t;
|
||
for (int i = 1; i <= m; i++) {
|
||
char str[5];
|
||
cin >> str;
|
||
int a = str[0] - 'A', b = str[2] - 'A';
|
||
|
||
// a<b,那么,不需要完全的重新计算完整的传递闭包,只需要把与a,b相关的变更进行记录大小关系即可
|
||
if (!type) {
|
||
g[a][b] = 1;
|
||
for (int x = 0; x < n; x++) {
|
||
if (g[x][a]) g[x][b] = 1; // 所有比a小的x,一定比b小
|
||
if (g[b][x]) g[a][x] = 1; // 所有比b大的x,一定比a大
|
||
for (int y = 0; y < n; y++)
|
||
if (g[x][a] && g[b][y])
|
||
g[x][y] = 1;
|
||
}
|
||
type = check();
|
||
if (type) t = i;
|
||
}
|
||
}
|
||
|
||
if (!type)
|
||
puts("Sorted sequence cannot be determined.");
|
||
else if (type == 2)
|
||
printf("Inconsistency found after %d relations.\n", t);
|
||
else {
|
||
string ans = getorder(); // 输出升序排列的所有变量
|
||
printf("Sorted sequence determined after %d relations: %s.\n", t, ans.c_str());
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
``` |