- [NOIP2010 提高组] 关押罪犯
- 题目背景
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 题解
- 思路分析
- 程序实现
[NOIP2010 提高组] 关押罪犯
题目背景
NOIP2010 提高组 T3
题目描述
S 城现有两座监狱,一共关押着 \(N\) 名罪犯,编号分别为 \(1\sim N\)。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 \(c\) 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 \(c\) 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 \(N\) 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
输入格式
每行中两个数之间用一个空格隔开。第一行为两个正整数 \(N,M\),分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 \(M\) 行每行为三个正整数 \(a_j,b_j,c_j\),表示 \(a_j\) 号和 \(b_j\) 号罪犯之间存在仇恨,其怨气值为 \(c_j\)。数据保证 \(1<a_j\leq b_j\leq N, 0 < c_j\leq 10^9\),且每对罪犯组合只出现一次。
输出格式
共一行,为 Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0
。
样例 #1
样例输入 #1
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
样例输出 #1
3512
提示
输入输出样例说明
罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是 \(3512\)(由 \(2\) 号和 \(3\) 号罪犯引发)。其他任何分法都不会比这个分法更优。
数据范围
对于 \(30\%\) 的数据有 \(N\leq 15\)。
对于 \(70\%\) 的数据有 \(N\leq 2000,M\leq 50000\)。
对于 \(100\%\) 的数据有 \(N\leq 20000,M\leq 100000\)。
题解
思路分析
【方法1】二分答案+染色二分图
题目中有明显的求最大值最小的概念,于是可以考虑二分答案,对于最大值检查可以采用二分图染色。
【方法2】贪心+并查集(带权/扩展域名)
由于题目的答案一定是其中的某一条边长,怎么使得该边长最小?
可以想到每次先将较大值放入不同监狱,如果可以放置,那么后面的答案一定更小;如果不能放置,则其就是答案。
所以可以按照边长排序,降序合并每条边即可。
合并的时候有两种策略
- 带权并查集:使用权值来维护当前元素与根节点元素的种类关系,如 d[u]=0表示 u与root是同类,d[u]=1表示不同类。带权并查集的find和union操作需要适当更新权值数组d[u];
int find(int u) {if (u != p[u]) {int fu = p[u];p[u] = find(p[u]);d[u] = (d[u] + d[fu]) % 2;}return p[u];
}
void union_(int u, int v) {int a = find(u), b = find(v);p[a] = b;d[a] = ((d[v] - d[u] + 2) % 2 + 1) % 2;
}
- 扩展域并查集:将并查集的规模扩展为原来的两倍,前一半用于维护朋友关系,后一半用于维护敌人关系。如 (u,u+n) 是敌人,(v,v+n) 是敌人,当 (u,v)是敌人的时候,根据敌人的敌人是朋友,可以将 u 与 u的敌人(v)的敌人(v+n)合并,即合并 (u, v+n);同理合并 (v, u+n)。
程序实现
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e4 + 10, M = 1e5 + 10, INF = 0x3f3f3f3f;
int n, m, p[N << 1], d[N << 1];
struct T {int u, v, w;bool operator<(const T& t) const { return w < t.w; }
} g[M];// -------------------
// 二分答案+染色二分图
vector<int> G[N];
int color[N], flag;
void dfs(int u, int c) {color[u] = c;for (auto v : G[u]) {if (!color[v]) dfs(v, ~c);else if (color[v] == c) flag = 0;}
}
bool chk(int k) { // 将边权 >k 的建图,检查是否可以进行二分图染色for (int i = 1; i <= n; i++) G[i].clear();for (int i = k + 1; i <= m; i++) {int u = g[i].u, v = g[i].v;G[u].push_back(v), G[v].push_back(u);}flag = 1, memset(color, 0x00, sizeof color);for (int i = 1; i <= n && flag; i++)if (!color[i]) dfs(i, 1);return flag;
}
int solve1() {int l = 0, r = m, ans = 0;while (l <= r) {int mid = l + r >> 1;chk(mid) ? r = mid - 1, ans = mid : l = mid + 1;}return g[ans].w;
}
// -------------------
int find(int u) {if (u != p[u]) {int fu = p[u];p[u] = find(p[u]);d[u] = (d[u] + d[fu]) % 2;}return p[u];
}
// 带权并查集
// d[i]表示 i与根节点的关系:0-朋友,1-敌人
int solve2() {for (int i = 0; i <= n; i++)p[i] = i, d[i] = 0;for (int i = m; i >= 1; i--) {int u = g[i].u, v = g[i].v;int a = find(u), b = find(v);if (a == b && (d[u] - d[v] + 4) % 2 == 0)return g[i].w;p[a] = b;d[a] = ((d[v] - d[u] + 2) % 2 + 1) % 2;}return 0;
}
// 扩展域并查集
// 敌人的敌人是朋友:u,u+n是敌人,u,v是敌人,将u放入v的敌人v+n的集合
int solve3() {for (int i = 0; i <= n * 2; i++)p[i] = i;for (int i = m; i >= 1; i--) {int u = g[i].u, v = g[i].v;int a = find(u), b = find(v);if (a == b)return g[i].w;p[a] = find(v + n);p[b] = find(u + n);}return 0;
}
int main() {cin >> n >> m;for (int i = 1, u, v, w; i <= m; i++)cin >> u >> v >> w, g[i] = {u, v, w};sort(g + 1, g + 1 + m);// cout << solve1() << endl;//AC// cout << solve2() << endl;//ACcout << solve3() << endl; // ACreturn 0;
}
/*
P1525 [NOIP2010 提高组] 关押罪犯
https://www.luogu.com.cn/problem/P1525in:
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884out:3512
*/