关押罪犯
关押罪犯
城现有两座监狱,一共关押着 名罪犯,编号分别为 。
他们之间的关系自然也极不和谐。
很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。
我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。
如果两名怨气值为 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 城 市长那里。
公务繁忙的 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 名罪犯间的矛盾关系后,警察局长觉得压力巨大。
他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。
假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
输入格式
第一行为两个正整数 和 ,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的 行每行为三个正整数 ,表示 号和 号罪犯之间存在仇恨,其怨气值为 。
数据保证 且每对罪犯组合只出现一次。
输出格式
输出共 行,为 市长看到的那个冲突事件的影响力。
如果本年内监狱中未发生任何冲突事件,请输出 。
数据范围
输入样例:
4 6 1 4 2534 2 3 3512 1 2 28351 1 3 6618 2 4 1805 3 4 12884
输出样例:
3512
解题思路
首先可以发现答案具有二段性,因此可以二分答案。假设最小的最大怒气值是,那么首先小于的最大怒气值是肯定取不到的,不然就与假设矛盾了。然后对于大于等于的怒气值是否一定存在一种分配方案得到的最大怒气值不超过这个值呢?由于当怒气值为时存在一种分配方案,因此当最大怒气值超过,存在该方案可以使得怒气值不超过最大怒气值。因此答案具有二段性。
当二分得到,由于允许的最大怒气值为,因此对于权值小于等于的边可以删除,保留大于的边。对于每条保留边我们要将边的两个端点分配到不同的监狱中(否则这两个点在同一个监狱,怒气最大求超过了)。为了知道能否将每条边的两个端点分配到不同的监狱,这里可以用带权并查集或染色法判定二分图来实现。
先介绍带权并查集的做法。
在并查集中,如果某个节点到其父节点的距离为(经过路径压缩后就是到根节点的距离),则表示该节点应该与其父节点必须分配到两个不同的监狱。如果某个节点到其父节点的距离为,则表示该节点应该与其父节点可以分配到两个相同的监狱。可以发现关系具有传递性,因此可以用带权并查集。
因此在函数中枚举每一条边,如果边的权值大于,则应该把这条边对应的两个端点(假设为和)进行合并,这里我们把所在的集合并到所在的集合:
由于和应该被分配到两个不同的监狱,因此和的距离应该相差,即,因此有。
如果和本身就在一个集合中,那么我们只需查看他们到根节点的距离是否相同,如果相同意味着和都应该与根节点在同一个监狱或不同的监狱,即和应该在同一个监狱,又由于这条边应该被保留,即和应该被分配到不同的监狱,因此就产生矛盾了。因此不存在一种分配方案使得怒气值最大为。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2e4 + 10, M = 1e5 + 10; 5 6 int n, m; 7 int a[M], b[M], c[M]; 8 int fa[N], dist[N]; 9 10 int find(int x) { 11 if (fa[x] == x) return fa[x]; 12 int p = find(fa[x]); 13 dist[x] ^= dist[fa[x]]; 14 return fa[x] = p; 15 } 16 17 bool check(int mid) { 18 for (int i = 1; i <= n; i++) { 19 fa[i] = i; 20 dist[i] = 0; 21 } 22 for (int i = 0; i < m; i++) { 23 if (c[i] > mid) { 24 int pa = find(a[i]), pb = find(b[i]); 25 if (pa != pb) { 26 fa[pa] = pb; 27 dist[pa] = dist[a[i]] ^ dist[b[i]] ^ 1; 28 } 29 else if (!(dist[a[i]] ^ dist[b[i]])) { 30 return false; 31 } 32 } 33 } 34 return true; 35 } 36 37 int main() { 38 scanf("%d %d", &n, &m); 39 for (int i = 0; i < m; i++) { 40 scanf("%d %d %d", a + i, b + i, c + i); 41 } 42 int l = 0, r = 1e9; 43 while (l < r) { 44 int mid = l + r >> 1; 45 if (check(mid)) r = mid; 46 else l = mid + 1; 47 } 48 printf("%d", l); 49 50 return 0; 51 }
再介绍染色法判定二分图的做法。
一样的对于每条保留的边由于我们要将两个端点分配到不同的监狱,假设存在一种分配方案满足所有边的要求,可以发现会形成一个二分图:
两个不同的监狱就是二分图中左右部的两个点集,其中边只存在与点集与点集之间,每个点集内部没有边。因此我们可以通过二染色来判断这个图是否为二分图,如果是则说明存在一种构造方案使得每条边的两个端点被分配在不同的监狱,否则说明不存在可行方案使得怒气值最大为。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2e4 + 10, M = 2e5 + 10; 5 6 int n, m; 7 int head[N], e[M], wts[M], ne[M], idx; 8 int color[N]; 9 10 void add(int v, int w, int wt) { 11 e[idx] = w, wts[idx] = wt, ne[idx] = head[v], head[v] = idx++; 12 } 13 14 bool dfs(int u, int c, int mid) { 15 color[u] = c; 16 for (int i = head[u]; i != -1; i = ne[i]) { 17 if (wts[i] <= mid) continue; 18 if (!color[e[i]] && !dfs(e[i], c ^ 3, mid) || color[e[i]] == c) return false; 19 } 20 return true; 21 } 22 23 bool check(int mid) { 24 memset(color, 0, sizeof(color)); 25 for (int i = 1; i <= n; i++) { 26 if (!color[i] && !dfs(i, 1, mid)) return false; 27 } 28 return true; 29 } 30 31 int main() { 32 scanf("%d %d", &n, &m); 33 memset(head, -1, sizeof(head)); 34 while (m--) { 35 int v, w, wt; 36 scanf("%d %d %d", &v, &w, &wt); 37 add(v, w, wt), add(w, v, wt); 38 } 39 int l = 0, r = 1e9; 40 while (l < r) { 41 int mid = l + r >> 1; 42 if (check(mid)) r = mid; 43 else l = mid + 1; 44 } 45 printf("%d", l); 46 47 return 0; 48 }
参考资料
AcWing 257. 关押罪犯(算法提高课):https://www.acwing.com/video/586/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17228020.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-03-17 剪格子
2021-03-17 一道Microsoft的笔试题目 —— Reversing Linked List