257 关押罪犯(二分 + 判断是否是二分图)

1. 问题描述:

S 城现有两座监狱,一共关押着 N 名罪犯,编号分别为 1∼N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用"怨气值"(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c 的冲突事件。每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。在详细考察了 N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

输入格式

第一行为两个正整数 N 和 M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 M 行每行为三个正整数 aj,bj,cj,表示 aj 号和 bj 号罪犯之间存在仇恨,其怨气值为 cj。数据保证 1≤ aj < bj < N,0 < cj ≤ 10 ^ 9 且每对罪犯组合只出现一次。

输出格式

输出共 1 行,为 Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0。

数据范围

N ≤ 20000,M ≤ 100000

输入样例:

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

输出样例:

3512
来源:https://www.acwing.com/problem/content/description/259/

2. 思路分析:

分析题目可以知道我们需要使得冲突事件的最大影响力最小,属于最大最小问题,根据最大最小问题所以我们考虑是否可以使用二分来解决,判断能否使用二分来解决主要是判断能否将答案确定在以mid的左边或者是右边,首先答案肯定是在[0,10 ** 9]范围,所有的罪犯都不会产生冲突那么影响力为0,如果将所有的罪犯都关在一起那么冲突事件的影响力最大为10 ** 9,所以答案肯定是在这个范围,我们可以采取这样一种策略:将无向图中所有小于等于mid的边都删除掉,其实在判断的时候忽略掉这些边即可,将所有大于mid的边对应的两个点分在两个不同的集合,也即将两个罪犯关押在不同的监狱,将大于mid的边分在两个不同的集合其实是将点排列成二分图,可以使用染色法判断当前的图是否是二分图,如果染色法没有矛盾说明当前的图是二分图那么就可以将所有大于mid的边对应的点分在两个不同的集合(一个图是二分图等价于染色法不存在矛盾),答案在mid的左边,如果不能将图中所有大于mid的边对应的点分在两个不同的集合说明影响力应该更大一点,也即答案在mid的右边,通过这样一个策略就可以将答案确定在mid左边或者是右边,所以可以使用二分来解决。因为数据规模比较大而且使用的是python语言,所以在染色的时候使用bfs来遍历节点并且对节点进行染色,这样可以避免dfs遍历节点并且对节点进行染色超时的问题。

3. 代码如下:

import collections
from typing import List


class Solution:
    # 因为边和节点的数量都比较多所以使用bfs来染色, python使用dfs来染色肯定会超时的
    def bfs(self, u: int, mid: int, color: List[int], g: List[List[int]]):
        # 将当前的节点加入到队列中
        q = collections.deque([u])
        # 将当前节点u染色
        color[u] = 1
        while q:
            p = q.popleft()
            for next in g[p]:
                # 跳过权重小于等于mid的边, 说明他们在同一个监狱或者是不同的监狱都没有影响
                if next[1] <= mid: continue
                # 将所有大于mid的边对应的另外一个节点进行染色
                if color[next[0]] == 0:
                    q.append(next[0])
                    color[next[0]] = 3 - color[p]
                # 相邻点的颜色相同说明存在矛盾
                elif color[next[0]] == color[p]:
                    return False
        return True

    # 将g中所有大于mid的边分到两个监狱也即判断是否是二分图
    def check(self, n: int, mid: int, g: List[List[int]]):
        color = [0] * (n + 10)
        for i in range(1, n + 1):
            # 0表示未染色, 1和2表示两种不同的颜色
            if color[i] == 0:
                # 染色的过程中发现矛盾直接返回False
                if not self.bfs(i, mid, color, g): return False
        return True

    def process(self):
        n, m = map(int, input().split())
        g = [list() for i in range(n + 10)]
        for i in range(m):
            a, b, c = map(int, input().split())
            # 无向边
            g[a].append((b, c))
            g[b].append((a, c))
        # 二分答案, 将所有权重大于mid分为两个监狱中也即判断是否是二分图
        l, r = 0, 10 ** 9
        while l < r:
            mid = l + r >> 1
            # 可以将所有大于mid的边分在两个不同的集合那么答案在mid左边, 否则在mid右边
            if self.check(n, mid, g):
                r = mid
            else:
                l = mid + 1
        return r


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

Guess you like

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