AcWing 343. 排序

\(AcWing\) \(343\). 排序

一、题目描述

给定 \(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\)

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\),如果用图形表示出这种大小关系,就是\(a\)\(b\)有一条有向边 【小的向大的连一条边】\(b\)\(c\)有一条有向边,可以推出\(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\)对二元关系就会出现矛盾也不去管它了。
  • 题目字母只会在\(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\)函数。

// 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\)之间的大小关系还没有确定下来,需要继续读取下一对二元关系
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;
}
posted @ 2022-03-20 09:52  糖豆爸爸  阅读(93)  评论(0编辑  收藏  举报
Live2D