P1347 排序

题目描述

一个不同的值的升序排序数列指的是一个从左到右元素依次增大的序列,例如,一个有序的数列 $A,B,C,D$ 表示 $A<B,B<C,C<D$。在这道题中,我们将给你一系列形如 $A<B$ 的关系,并要求你判断是否能够根据这些关系确定这个数列的顺序。

输入格式

第一行有两个正整数 $n,m$,$n$ 表示需要排序的元素数量,$2\leq n\leq 26$,第 $1$ 到 $n$ 个元素将用大写的 $A,B,C,D,\dots$ 表示。$m$ 表示将给出的形如 $A<B$ 的关系的数量。

接下来有 $m$ 行,每行有 $3$ 个字符,分别为一个大写字母,一个 < 符号,一个大写字母,表示两个元素之间的关系。

输出格式

若根据前 $x$ 个关系即可确定这 $n$ 个元素的顺序 yyy..y(如 ABC),输出

Sorted sequence determined after xxx relations: yyy...y.

若根据前 $x$ 个关系即发现存在矛盾(如 $A<B,B<C,C<A$),输出

Inconsistency found after x relations.

若根据这 $m$ 个关系无法确定这 $n$ 个元素的顺序,输出

Sorted sequence cannot be determined.

(提示:确定 $n$ 个元素的顺序后即可结束程序,可以不用考虑确定顺序之后出现矛盾的情况)

输入输出样例 #1

输入 #1

4 6
A<B
A<C
B<C
C<D
B<D
A<B

输出 #1

Sorted sequence determined after 4 relations: ABCD.

输入输出样例 #2

输入 #2

3 2
A<B
B<A

输出 #2

Inconsistency found after 2 relations.

输入输出样例 #3

输入 #3

26 1
A<Z

输出 #3

Sorted sequence cannot be determined.

说明/提示

$2 \leq n \leq 26,1 \leq m \leq 600$。

【题解】
本人也用到了拓扑排序和动态规划的方法。 具体而言,运用拓扑排序将点的坐标存入一个数组中。每到一个点,将其所有出边所连的点的入度减 1 (可以理解为删除该点)。如果该点出边所连的点入度被减 0 ,那么将该点出边所连的点加入队列,表示作为没有删除条件了的点准备被删除。当一点枚举完所有出边,则将此点放入上述数组中(拓扑排序基本原理)。这样一来,从头至尾遍历该数组时,因为运用拓扑排序,每一个点入边所连的点一定都不在其后面被遍历。读者不难想到,如果让所有会影响某一节点的其他点的值都不再改变,则这个节点也必将在更新它的邻点时不会改变。这就很符合动态规划的无后效性的特点。直白的说,是拓扑排序给一张图提供了天生的符合使用动态规划的遍历顺序,因此考虑将二者结合使用求解最长路。

针对本题而言,唯一难点在于要求从 1 开始的最长路。拓扑排序和动态规划模板是求解全图中到达某点的最长路。核心问题是节点 1 可能不是该路线的起点,或者会有一条路不经过节点 1 而到达 n 点且最长。因此我们考虑能否将节点 1 看作是起点。

我思考的方法是,利用数组 str 存储是否以 1 为起点。遍历图到节点 1 时,将 str 的第一个设为以 1 为起点。如果再后来碰到父节点 str 值为 1 时,则子节点也被认为是以 1 为起点的点。遍历图结束后,只有我们想要进行动态规划的路径上(也就是以 1 为起点能到达的所有路径上, str 都是 1 )。因为拓扑排序处理有向无环图,显然从 str 值为 1 的点开始,不可能到达 str 值为 0 的点。在进行动态规划时,依照拓扑排序的顺序遍历,在 str 值为 0 的点时直接 continue ,让这些点无法给有用路径上更新长度,就实现了将其余路径排除在外,只考虑以 1 为起点的路径上的长度更新。 (分析冗长,原理简单)

另外注意有负权边要初始化动态规划数组。

代码:

#include <iostream>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;
int n,m;
int x,y,z;
//对于这个有向无环图,每一条先驱路径都会有改变该点最长路的可能性,拓扑排序保证后面的所有的先驱点都已经发更新完成不会再对他造成影响

struct node{
    int v, w, next;
}e[500005];
int head[100005], cnt;

int tmp = 0; 
int in[100005];//入度 
int px[100050];//存排序编号,便于dp
int dp[100005];//表示1到i点的最长路径 
bool str[100005];//记录所有由以1为起点的点 

void add(int u, int v, int w){
    cnt++;
    e[cnt].v = v;
    e[cnt].w = w; 
    e[cnt].next = head[u];
    head[u] = cnt;
}

queue<int> q;

void tuopu(){
    for(int i = 1; i <= n; i ++){
        if(in[i] == 0){
            q.push(i); 
        }
    }
    while(q.empty() == 0){
        int u = q.front();
        q.pop();
        if(u == 1){ 
            str[u] = 1;
        }
        for(int i = head[u]; i != 0; i = e[i].next){
            int v = e[i].v;
            in[v] --;
            if(str[u] == 1){ 
                str[v] = 1;
            }//可以推广 
            if(in[v] == 0){
                q.push(v);
            }
        }
        px[++tmp] = u;
    }
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= m; i ++){
        cin >> x >> y >> z;
        add(x, y, z);
        in[y] ++;
    }
    
    tuopu();
    
    for(int i = 1; i <= n; i ++){
        dp[i] = -1e9;
    }
    dp[1] = 0;
    
    for(int i = 1; i <= n; i ++){
        int u = px[i];
        if(str[u] == 0){//不在1路上,不用更新来自这条路的路径长度 
            continue;
        }
        //每一个点按照顺序,对其周围的点进行更新,排序目的是使得所有点更新别的点时不会再被更新
        for(int j = head[u]; j != 0; j = e[j].next){
            int v = e[j].v;
            dp[v] = max(dp[v], dp[u] + e[j].w);
        }
    }
    
    if(n == 1){
        cout << 0 << endl;
        return 0;
    } 
    if(dp[n] != -1e9){
        cout << dp[n] << endl;
    }else{
        cout << -1 << endl;
    }
    return 0;
}

Logo

集算法之大成!助力oier实现梦想!

更多推荐