【ACWing】343. 排序

题目地址:

https://www.acwing.com/problem/content/345/

给定 n n n个变量和 m m m个不等式。其中 n n n小于等于 26 26 26,变量分别用前 n n n的大写英文字母表示。不等式之间具有传递性,即若 A > B A>B A>B B > C B>C B>C,则 A > C A>C A>C。请从前往后遍历每对关系,每次遍历时判断:如果能够确定全部关系且无矛盾,则结束循环,输出确定的次序;如果发生矛盾,则结束循环,输出有矛盾;如果循环结束时没有发生上述两种情况,则输出无定解。

输入格式:
输入包含多组测试数据。每组测试数据,第一行包含两个整数 n n n m m m。接下来 m m m行,每行包含一个不等式,不等式全部为小于关系。当输入一行 0 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 2≤n≤26 2n26,变量只可能为大写字母 A ∼ Z A∼Z AZ

本质上是在求传递闭包,传递闭包是指,对于一个有向图,如果存在 a → b a\to b ab的路径,那么就一定存在 a a a b b b的一条边。在每加入一条边的时候,就求一下是否存在合法的传递闭包,即不能出现 x < x x<x x<x,并且对任意 x , y x,y x,y,都有 x < y x<y x<y y < x y<x y<x。如果所有边加上去都不能导致后者成立,则说明不能决定。求传递闭包可以用类似Floyd算法的办法,设 d [ x ] [ y ] = 1 d[x][y]=1 d[x][y]=1代表已经知道 x < y x<y x<y d [ x ] [ y ] = 0 d[x][y]=0 d[x][y]=0代表 x < y x<y x<y还未决定。加上一条边 ( a , b ) (a,b) (a,b)的时候,就知道了 d [ a ] [ b ] = 1 d[a][b]=1 d[a][b]=1,此外对所有 x → a x\to a xa d [ x ] [ b ] = 1 d[x][b]=1 d[x][b]=1,对所有 b → y b\to y by d [ a ] [ y ] = 1 d[a][y]=1 d[a][y]=1,同时还要更新 d [ x ] [ y ] = 1 d[x][y]=1 d[x][y]=1。每次加上一条边的时候,就判断一下是否已经存在合法的传递闭包了,如果存在,就逐次求未标记的点中的最小值,并每次把当前最小值标记下。这些最小值的序列就是一个全序序列。

那么是否存在一个合法的传递闭包,就一定存在一个全序序列呢(也就是上面的步骤是否每次都能找到最小值呢)。答案是肯定的。否则的话,就可以找到一个无穷序列,使得 . . . > x k > x k − 1 > . . . > x 2 > x 1 ...>x_k>x_{k-1}>...>x_2>x_1 ...>xk>xk1>...>x2>x1,从而有环,就能推出存在某个 x x x使得 d [ x ] [ x ] = 1 d[x][x]=1 d[x][x]=1,这就与传递闭包合法矛盾了。

代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 26;
int n, m;
int d[N][N];
bool st[N];

int check() {
	// 首先不能有x < x
    for (int i = 0; i < n; i++)
        if (d[i][i]) return -1;

	// 其次不能存在两个不可比较的点
    for (int i = 0; i < n; i++)
        for (int j = 0; j < i; j++)
            if (!d[i][j] && !d[j][i])
                return 0;

    return 1;
}

char get() {
	// 找到一个极小元。由于在传递闭包合法的情况下存在全序,所以极小元也就是最小元
    for (int i = 0; i < n; i++) {
        if (st[i]) continue;
        bool found = false;
        for (int j = 0; j < n; j++)
            if (!st[j] && d[j][i]) {
                found = true;
                break;
            }

        if (!found) {
            st[i] = true;
            return 'A' + i;
        }
    }

    return 0;
}

int main() {
    while (cin >> n >> m, n || m) {
        int type = 0, t;
        memset(d, 0, sizeof d);
        for (int i = 1; i <= m; i++) {
            char str[5];
            cin >> str;
            int a = str[0] - 'A', b = str[2] - 'A';
            if (!type) {
            	// 读入一条边,并更新传递闭包
                d[a][b] = 1;
                for (int x = 0; x < n; x++) {
                    if (d[x][a]) d[x][b] = 1;
                    if (d[b][x]) d[a][x] = 1;
                    for (int y = 0; y < n; y++)
                        if (d[x][a] & d[b][y])
                            d[x][y] = 1;
                }
                    
                type = check();
                // 记录一下产生矛盾或找到合法传递闭包的轮数,
                // 注意此时不能跳出循环,而要把接下来的边都读下来
                if (type) t = i;
            }
        }

        if (type == -1) printf("Inconsistency found after %d relations.\n", t);
        else if (type == 0) printf("Sorted sequence cannot be determined.\n");
        else {
            memset(st, 0, sizeof st);
            printf("Sorted sequence determined after %d relations: ", t);
            for (int i = 0; i < n; i++)
                printf("%c", get());
            printf(".\n");
        }
    }

    return 0;
}

时间复杂度 O ( m n 2 ) O(mn^2) O(mn2)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值