算法
(二分,染色法判断二分图) $O((N + M)logC)$
将罪犯当做点,罪犯之间的仇恨关系当做点与点之间的无向边,边的权重是罪犯之间的仇恨值。
那么原问题变成:将所有点分成两组,使得各组内边的权重的最大值尽可能小。
我们在 $[0, 10^9]$ 之间枚举最大边权 $limit$,当 $limit$ 固定之后,剩下的问题就是:
- 判断能否将所有点分成两组,使得所有权值大于 $limit$ 的边都在组间,而不在组内。也就是判断由所有点以及所有权值大于 $limit$ 的边构成的新图是否是二分图。
判断二分图可以用染色法,时间复杂度是 $O(N + M)$,其中 $N$ 是点数,$M$ 是边数,可以参考AcWing 860. 染色法判定二分图。
为了加速算法,我们来考虑是否可以用二分枚举 $limit$, 假定最终最大边权的最小值是 $Ans$:
- 那么当 $limit \in [ans, 10^9]$ 时,所有边权大于 $limit$ 的边,必然是所有边权大于 $Ans$ 的边的子集,因此由此构成的新图也是二分图。
- 当 $limit \in [0, ans - 1]$ 时,由于 $ans$ 是新图可以构成二分图的最小值,因此由大于 $limit$ 的边构成的新图一定不是二分图。
- 所以整个区间具有二段性,可以二分出分界点 $ans$ 的值。二分算法模板可以参考这篇。
时间复杂度分析
总共二分 $logC$ 次,其中 $C$ 是边权的最大值,每次二分使用染色法判断二分图,时间复杂度是 $O(N + M)$,其中 $N$ 是点数,$M$ 是边数。因此总时间复杂度是 $O((N + M)logC)$。
C++ 代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 20010, M = 200010;
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, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool dfs(int u, int c, int limit)
{
color[u] = c;
for (int i = h[u]; ~i; i = ne[i])
{
if (w[i] <= limit) continue;
int j = e[i];
if (color[j])
{
if (color[j] == c) return false;
}
else if (!dfs(j, 3 - c, limit)) return false;
}
return true;
}
bool check(int limit)
{
memset(color, 0, sizeof color);
for (int i = 1; i <= n; i ++ )
if (color[i] == 0)
if (!dfs(i, 1, limit))
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;
}
printf("%d\n", l);
return 0;
}
贴一个用并查集的,时间复杂度$O(mlogm)$
tql
重载运算符这里和快排用的很妙
这里为什么还要sort啊?感觉没什么影响啊?
# Y总太强了
## ○| ̄|_
是
colour
不是color
,世界是用的英式英语纠结这个不如多刷两个题
XD
○| ̄|_
y总请问我这个做法问题在哪呢?谢谢
你的dfs函数的第一个if那里,应该写成 if(color[(it).to]==0){ if(!dfs((it).to,3-c)) return false};
谢谢!
y总太强了,学了半年就已经可以写出这个题了,我学了一年还是什么都不会
啊?真的吗,y总在哪说的呀请问
代码完全y风hh
说一下if (w[i] < limit) continue;和if (w[i] <= limit) continue;这两种情况,前者二分的是二分图两个集合之间的边,后者二分的是二分图内部的边,前者二分出来的正好是答案,后者二分出来的应该正好大于答案,所以减一之后就是答案
是不是说反了,还是说我理解错了= =
不好意思是说反了
前者减一是答案,后者是答案
这些题到底是无向图还是有向图
为啥改为if (w[i] < limit) continue;就错了捏
就是为啥在二分的时候所有大于mid的可以分到二分图中,而所有大于等于mid的分到二分图中就是错的?
因为limit这条边要在两组其中的一组内
因为mid是符合条件的最大值,也就是要在一个集合中
%%%
我龙傲天佩服!
%%%
这就是京东笔试题
太强啦,y总!
没想到这道题是这样做的。
蒟蒻求助……
判断那边应该是
3-col
把这里二分的结果是怎么保证一定是所给的那些边里面的啊
二分的结果如果不是某条边的权值,那就必然不是最小值,因为可以将这个结果减小到某条边的权值。
y总的思路真是太妙了囧rz
y总,想问一下 if (w[i] <= limit) continue; 的作用是什么
如果那俩个点的边小于当前的限制的话,那么这俩个点在不在二分图的一边不会影响结果,所以跳过这一条边
嗯呢,谢谢呀
这里二分的是监狱内部最大边的权值,如果边小于等于limit,那么这俩点可以在一个监狱里,只有大于limit时才不能在监狱里。所以可以把小于等于limit的点跳过。
懂了,谢谢y总
为什么注释掉这句只能过一个测试点呢?
题的关键就在于权值大于limit(二分枚举的数)的边是不是能构成二分图,注释掉就没有意义了,就相当于看全部的边能不能构成二分图了