You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 343. 排序

一、题目描述

给定 n 个变量和 m 个不等式。其中 n 小于等于 26,变量分别用前 n 的大写英文字母表示。

不等式之间具有传递性,即若 A>BB>C,则 A>C

请从前往后遍历每对关系,每次遍历时判断:

  • 如果能够确定全部关系且无矛盾,则结束循环,输出确定的次序;
  • 如果发生矛盾,则结束循环,输出有矛盾;
  • 如果循环结束时没有发生上述两种情况,则输出无定解。

输入格式 输入包含多组测试数据。

每组测试数据,第一行包含两个整数 nm

接下来 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,变量只可能为大写字母 AZ

输入样例1

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

Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.

输入样例2

6 6
A<F
B<D
C<E
F<D
D<E
E<F
0 0

输出样例2

Inconsistency found after 6 relations.

输入样例3

5 5
A<B
B<C
C<D
D<E
E<A
0 0

输出样例3

Sorted sequence determined after 4 relations: ABCDE.

二、floyd 求传递闭包

概念 给定若干对元素和若干对二元关系,并且关系具有传递性,通过传递性推导出尽量多的元素之间关系的问题被称为 传递闭包

解释:比如a < b,b < c,就可以推导出a < c,如果用图形表示出这种大小关系,就是ab有一条有向边 【小的向大的连一条边】bc有一条有向边,可以推出a可以到达c,找出图中各点能够到达点的集合,类似floyd算法求图中任意两点间的最短距离 【魔改版的floyd

模板

//传递闭包
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对二元关系就会出现矛盾也不去管它了。
  • 题目字母只会在AZ间,因此可以映射为02526个元素
  • 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函数。

// 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 表示ij之间的大小关系还没有确定下来,需要继续读取下一对二元关系
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)

#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

#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;
}