362 区间(差分约束)

1. 问题描述:

给定 n 个区间 [ai,bi] 和 n 个整数 ci。你需要构造一个整数集合 Z,使得 ∀i∈[1,n],Z 中满足 ai ≤ x≤ bi 的整数 x 不少于 ci 个。求这样的整数集合 Z 最少包含多少个数。

输入格式

第一行包含整数 n。接下来 n 行,每行包含三个整数 ai,bi,ci。

输出格式

输出一个整数表示结果。

数据范围

1 ≤ n ≤ 50000,
0 ≤ ai,bi ≤ 50000,
1 ≤ ci ≤ bi − ai + 1

输入样例:

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

输出样例:

6
来源:https://www.acwing.com/problem/content/description/364/

2. 思路分析:

这道题目其实有两个解决方法,分别是:① 贪心 ② 差分约束,下面使用差分约束来求解。差分约束的难点在于找全题目中所有的不等式关系,这里需要使用前缀和的思想,Si表示从1~i中选出来的数的个数,因为区间右端点的范围是50000,这里为了空出0号点的位置所以我们将所有输入的区间端点都往后移动一位,对求解的答案是没有什么影响,所以最终的答案是dis[50001],因为求解的是最小值所以我们需要使用单源最长路径来求解,根据题目的描述找到的不等式关系如下:

  • Si >= Si-1,1 <= i <= 50001,==> Si >= Si-1表示我们从第i个节点选的数的个数应该大于等于上一个节点的个数
  • Si - Si-1 <= 1,1 <= i <= 50001,==> Si-1 >= Si - 1,表示第i个数字选还是没选
  • [a,b]至少选择c个,也即Sb - Sa-1 >= c ==> Sb >= Sa-1 + c

我们需要验证一下限制条件够不够,感觉应该是够的,并且需要验证是否存在源点使得可以到达所有的边,从第一个条件可以发现我们其实是从0号点出发最终是可以到达50001号点的,也即可以到达所有边,所以0号点就是源点。在建图的时候将不等号右边节点向左边节点连一条边,为什么第二个条件第i个节点需要向i-1个节点连一条-1的边,也即为什么要加这个限制呢?其实不加是有问题的,例如下面的数据,如果在建图的时候不加:g[i].append((i - 1, -1))那么答案为151,而正确的答案是157,当第i个节点没有向第i - 1个节点连边的时候那么有些节点的最大值没有更新导致答案错误,所以在限制条件的时候要找全。

5
127 241 56
29 48 12
84 242 139
30 42 10
174 248 75

3. 代码如下:

import collections
from typing import List


class Solution:
    def spfa(self, g: List[List]):
        INF = 10 ** 10
        dis = [-INF] * 50010
        dis[0] = 0
        q = collections.deque()
        q.append(0)
        vis = [0] * 50010
        vis[0] = 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]
                    if vis[next[0]] == 0:
                        vis[next[0]] = 1
                        q.append(next[0])
        # 最后一个点就是答案
        return dis[50001]

    def process(self):
        n = int(input())
        # 因为区间端点最大为50000所以声明大一点的即可
        g = [list() for i in range(50010)]
        for i in range(1, 50002):
            g[i - 1].append((i, 0))
            g[i].append((i - 1, -1))
        for i in range(n):
            a, b, c = map(int, input().split())
            # 为了让0号点空出来所有区间端点的位置向后移动一位, 对答案没有什么影响的
            a += 1
            b += 1
            g[a - 1].append((b, c))
        return self.spfa(g)


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

Guess you like

Origin blog.csdn.net/qq_39445165/article/details/121174620