关押罪犯(二分图+二分 or 扩展域并查集)

本文介绍了如何运用二分图和二分查找算法解决NOIP2010提高组的关押罪犯问题。通过对罪犯之间的仇恨值进行分析,确定能形成二分图的最大仇恨值,从而达到将罪犯分配到两个房间以使内部最大仇恨值最小化的目标。文章提供了两种解法,分别是C++实现的二分图染色法和并查集方法,并给出了详细的代码实现和思路解析。

题目描述

NOIP2010提高组关押罪犯(二分图+二分)
简言之,就是一个监狱里有若干罪犯,他们两两之间存在的一个仇恨值。监狱里有两个房间,需要把这么一些罪犯分到两边,来使房间内两两之间最大的仇恨值最小。

首先为什么想到二分图?因为目标是要找到一个最小的仇恨值,那么大于这个仇恨值关系的罪犯,一定可以被分到两边,即他们之间的关系是组与组之间的关系,可以形成一个二分图。一旦小于这个仇恨值,就会有组内之间的边出现,无法构成二分图。

经过上面的描述,我们很容易分析出,仇恨值存在二段性,大于最小仇恨值可以形成二分图,小于等于则无法形成二分图。因此想到用二分

解法一:C++(二分图+二分)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20010, M = 2e5 + 10;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int color[N];

void add(int a, int b, int c) {
	e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
// ---------------染色法判断二分图-----------------
bool dfs(int u, int c, int mid) {
	color[u] = c;
	
	for (int i = h[u]; ~i; i = ne[i]) {
		if (w[i] <= mid) continue;
		int j = e[i];
		if (!color[j]) {
			if (!dfs(j, 3-c, mid)) return false;
		} else if (color[j] == c) return false;
	}
	return true;
}

bool check(int mid) {
	memset(color, 0, sizeof color);
	
	for (int i = 1; i <= n; i++) 
		if (!color[i]) 
			if (!dfs(i, 1, mid)) 
			    return false;

	return true;
}
// ----------------------------------------------
int main() {
	scanf("%d%d", &n, &m);
	memset(h, -1, sizeof h);
	while (m--) {
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c), add(b, a, c); // 双向边
	}
	
	// 二分
	int l = 0, r = 1e9;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	
	cout << r << endl;
	
	return 0;
}

解法二:种类并查集

a+n代表a的虚点。当a与b是敌对关系时,就把a与b+n,a+n与b并在一起

往后判断是否连在了同一个虚点上即可

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m;
const int N = 2e4 + 10, M = 1e5 + 10;
int p[N * 2];

struct Node {
    int a, b, c;
    bool operator<(const Node& w) const {
        return c > w.c;
    }
}node[M];

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    scanf("%d%d", &n, &m);
    
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        node[i] = {a, b, c};
    }
    
    for (int i = 1; i <= n * 2; i++) p[i] = i;
    
    sort(node + 1, node + 1 + m);
    
    for (int i = 1; i <= m; i++) {
        if (find(node[i].a) == find(node[i].b) || find(node[i].a+n) == find(node[i].b+n)) {
            cout << node[i].c << endl;
            return 0;
        }
        p[find(node[i].a)] = find(node[i].b + n);
        p[find(node[i].a+n)] =  find(node[i].b);
    }
    
    puts("0");
    
    return 0;
}

参考资料

AcWing 257. 关押罪犯-作者yxc

### 使用带权并查集解决关押罪犯问题 为了有效处理罪犯之间的矛盾关系并将这些罪犯合理分配到两所不同的监狱中,可以采用带权并查集来解决问题[^2]。 #### 带权并查集简介 带权并查集是一种扩展版本的并查集数据结构,在普通的查找和合并操作基础上增加了权重管理功能。对于本题而言,权重用于表示当前节点与其父节点所在集合的关系(即同属一个监狱还是不同监狱)。具体来说: - 如果两个节点属于同一个监狱,则它们之间路径上的所有边权重之积应为正数; - 若两者位于不同监狱,则该乘积应当是负数; 这种特性使得可以通过简单的判断快速得知任意两名罪犯是否能被安置在同一所监狱里而不会引发冲突。 #### 解决方案描述 针对题目中的需求——尽可能减少因仇恨而导致的摩擦次数,采取如下策略: 1. 预先读取输入数据,构建初始状态下的并查集实例; 2. 对每一对存在敌意的罪犯组合按照其对应的愤怒值降序排列; 3. 尝试依次处理上述列表里的每一项记录: - 利用`find()`函数定位双方所属根结点及其相对位置信息; - 当发现二者已经处在相同连通分支下时停止进一步尝试,并返回此时的最大未处理愤怒值作为最终结果; 4. 成功完成全部配对后输出0表明不存在无法调和的情况; 通过这种方式可以在O(nlogn)复杂度内高效地找到最优解法[^3]。 ```python class UnionFindWithWeight: def __init__(self, n): self.parent = list(range(n)) self.weight = [1] * n def find(self, x): if self.parent[x] != x: root = self.find(self.parent[x]) self.weight[x] *= self.weight[self.parent[x]] self.parent[x] = root return self.parent[x] def union(self, x, y, w): rx, ry = map(self.find, (x, y)) if rx == ry: return abs(self.weight[x]*w*self.weight[y]) == 1 self.parent[rx] = ry self.weight[rx] = self.weight[y] * w * self.weight[x] def solve_prisoner_conflict(N, M, conflicts): uf = UnionFindWithWeight(N+1) # Sort by weight in descending order. sorted_conflicts = sorted(conflicts, key=lambda item:item[-1], reverse=True) for u,v,w in sorted_conflicts: if not uf.union(u, v, -1): return w return 0 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeSlogan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值