1170 排队布局(差分约束)

1. 问题描述:

当排队等候喂食时,奶牛喜欢和它们的朋友站得靠近些。农夫约翰有 N 头奶牛,编号从 1 到 N,沿一条直线站着等候喂食。奶牛排在队伍中的顺序和它们的编号是相同的。因为奶牛相当苗条,所以可能有两头或者更多奶牛站在同一位置上。如果我们想象奶牛是站在一条数轴上的话,允许有两头或更多奶牛拥有相同的横坐标。一些奶牛相互间存有好感,它们希望两者之间的距离不超过一个给定的数 L。另一方面,一些奶牛相互间非常反感,它们希望两者间的距离不小于一个给定的数 D。给出 ML 条关于两头奶牛间有好感的描述,再给出 MD 条关于两头奶牛间存有反感的描述。你的工作是:如果不存在满足要求的方案,输出-1;如果 1 号奶牛和 N 号奶牛间的距离可以任意大,输出-2;否则,计算出在满足所有要求的情况下,1 号奶牛和 N 号奶牛间可能的最大距离。

输入格式

第一行包含三个整数 N,ML,MD。接下来 ML 行,每行包含三个正整数 A,B,L,表示奶牛 A 和奶牛 B 至多相隔 L 的距离。再接下来 MD 行,每行包含三个正整数 A,B,D,表示奶牛 A 和奶牛 B 至少相隔 D 的距离。

输出格式

输出一个整数,如果不存在满足要求的方案,输出-1;如果 1 号奶牛和 N 号奶牛间的距离可以任意大,输出-2;否则,输出在满足所有要求的情况下,1 号奶牛和 N 号奶牛间可能的最大距离。

数据范围

2 ≤ N ≤ 1000,
1 ≤ ML,MD ≤ 10 ^ 4,
1 ≤ L,D ≤ 10 ^ 6

输入样例:

4 2 1
1 3 10
2 4 20
2 3 3

输出样例:

27
来源:https://www.acwing.com/problem/content/description/1172/

2. 思路分析:

分析题目可以知道奶牛是按照1~N的顺序依次排列的,并且某些奶牛之间有某些距离上的限制,我们可以将这些限制转化成对应的不等式关系,这样就可以将原问题转化为差分约束的问题,根据题目中的限制关系我们可以找到下面的不等式关系,因为求解的是最大值所以我们需要求解单源最短路径,此时不等式要转换为小于号的形式:

  • 奶牛按照1~N的顺序进行排列的,也即xi <= xi+1,1 <= i < n ==> i + 1向i连一条权重为0的边
  • xb - xa <= L ==> xb <= xa + L;a向b连一条权重为L的边
  • xb - xa >= D ==> xa <= xb - D;b向a连一条权重为-D的边

还需要验证一点是否存在一个源点可以到达所有的边,可以发现n号点是可以到所有点的,也即可以到达所有边,但是为了方便编写代码我们其实可以建一个虚拟源点,这样两次调用spfa的代码都是一样的,这样就不用写两次spfa代码了;对于问题1我们判断是否存在负环即可,此时需要建立一个虚拟源点,我们可以将0号点作为虚拟源点,向各个点建一条边权为0的边,而对于spfa算法来说其实等价于将所有点入队;对于问题2和问题3我们需要求解1号点到n号点的最短距离,判断dis[n]是否是正无穷,如果是正无穷说明xn <= x1 ....的不等式链是不存在的也即xn与x1的距离是没有限制的,返回-2,如果不是正无穷返回对应的距离即可,由题目可知我们求解的其实是相对距离,所以可以设置到1号点的距离为0(如果设置0号点为其他值也是可以的,最终返回的答案减去对应值即可),对答案是没有什么影响的。

3. 代码如下:

import collections
from typing import List


class Solution:
    def spfa(self, n: int, size: int, g: List[List[int]]):
        INF = 10 ** 10
        dis = [INF] * (n + 1)
        vis = [0] * (n + 1)
        count = [0] * (n + 1)
        q = collections.deque()
        for i in range(1, size + 1):
            q.append(i)
            dis[i] = 0
            vis[i] = 1
        while q:
            p = q.popleft()
            vis[p] = 0
            for next in g[p]:
                if dis[next[0]] > dis[p] + next[1]:
                    dis[next[0]] = dis[p] + next[1]
                    count[next[0]] = count[p] + 1
                    if count[next[0]] >= n:
                        return -1
                    if vis[next[0]] == 0:
                        q.append(next[0])
                        vis[next[0]] = 1
        return dis[n]

    def process(self):
        n, m1, m2 = map(int, input().split())
        g = [list() for i in range(n + 10)]
        for i in range(1, n):
            # i + 1向i连一条0的边
            g[i + 1].append((i, 0))
        for i in range(m1):
            # a向b连一条c的边
            a, b, c = map(int, input().split())
            g[a].append((b, c))
        for i in range(m2):
            # b向a连一条-c的边
            a, b, c = map(int, input().split())
            g[b].append((a, -c))
        INF = 10 ** 10
        # 判断是否存在正环这个时候需要将所有节点加入到队列中, 因为源点向其余点连一条边权为0的边所以对于spfa来说相当于是将所有点入队, size = n
        if self.spfa(n, n, g) == -1: return -1
        # 求解从1号点到n到点的最短距离, 此时只有1号点入队, size = 1
        res = self.spfa(n, 1, g)
        if res == INF: return -2
        else: return res


if __name__ == "__main__":
    print(Solution().process())

你可能感兴趣的:(acwing-提高,算法)