目录
篛箕的自诉
题目分析+代码:
第一种做法(图论+搜索):
第二种做法(并查集,拓展域):
篛箕的自诉
以前的我对你爱搭不理,现在的我对你笔不能提:
传送门
以前学习搜索的时候看到这个题就感觉一定会做,对他爱搭不理。到现在回过头来看的时候,却发现自己已经忘记怎么做了(自己真是个弱鸡)。
题目分析+代码:
第一种做法(图论+搜索):
这个题给了我们一个图(不会图论的可以看下一种方法),对于每个节点的边权,我们可以将这个图分成两份,使得这两个图的边权尽可能的小,求这个最小值的最大值;
一想到最小值的最大值,这不就是二分答案吗?然而,问题的关键给到了cheak函数上,对于一个 mid ,我们要如何来确定这个mid的可行性?
首先,对于一个图,我们把边权大于 mid 的节点分开到两个监狱里面,边权小于等于mid的就不需要判断了,然后判断是否能把这个图分成二分图(又是一个图论的知识),这就可以用上板子了,判断一个图能否二分的板子:
博主的二分图板子
这样就顺理成章的写出了nlogn复杂度的二分加二分图的判定,代码如下:
#include#define INF 0x3f3f3f3f #define mem(arr, num) memset(arr, num, sizeof arr) using namespace std; const int N = 2e5 + 10, M = 1e6 + 10; int n, m, e[M], ne[M], w[M], h[N], 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 != -1; i = ne[i]) { int j = e[i]; if (w[i] <= mid) continue; if (color[j]) { if (color[j] == color[u]) return 0; } else if (!dfs(j, 3 - c, mid)) return 0; } return 1; } bool cheak(int mid)//判断这个数值能否将这个图分成两幅图; { mem(color, 0); for (int i = 1; i <= n; i ++ ) { if (!color[i]) if (!dfs(i, 1, mid)) return 0; } return 1; } int main() { mem(h, -1); scanf("%d%d", &n, &m); 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 (cheak(mid)) r = mid; else l = mid + 1; } printf("%dn", r); return 0; }
第二种做法(并查集,拓展域):
如果不会的可以先去看看这个题:食物链,很经典的并查集拓展域。
对于一对罪犯,如果他们的仇恨值较大的话,我们当然想把他们拆开,这样得到的仇恨值肯定会相应变小,我们就用这样的贪心的思路,来拆开仇恨值较大的两对罪犯。当我们拆到不能再拆的时候,那么这一对罪犯的仇恨值就是最后的仇恨值。
那么如何才能拆开一对罪犯呢?这时候就想到了并查集的扩展域,用一个域表示一个监狱,当我们拆分到不能拆分的时候输出这个仇恨值。
#include#define INF 0x3f3f3f3f #define mem(arr, num) memset(arr, num, sizeof arr) using namespace std; const int N = 1e6 + 10; int n, m, p[N]; struct node { int a, b, w; } rela[N]; bool cmp(node x, node y) { return x.w > y.w; } int finds(int x) { if (p[x] != x) p[x] = finds(p[x]); return p[x]; } void init() { for (int i = 1; i <= 2 * n; i ++ ) p[i] = i; } int main() { scanf("%d%d", &n, &m); init(); for (int i = 0; i < m ; i ++ ) scanf("%d%d%d", &rela[i].a, &rela[i].b, &rela[i].w); sort(rela, rela + m, cmp); for (int i = 0; i < m; i ++ ) { int a = rela[i].a, b = rela[i].b, w = rela[i].w; if (finds(a) == finds(b) || finds(a + n) == finds(b + n)) { cout << w << endl; return 0; } p[finds(a)] = finds(b + n), p[finds(b)] = finds(a + n); } cout << 0 << endl; return 0; }
这里,有一个很坑的点(我就被坑了很久),如果在循环里没有输出的话,这时候就代表两个监狱能够使所有有仇恨值的罪犯分开,这时候自然而然最小仇恨值为0。


![[NOIP2010]关押罪犯(题解) [NOIP2010]关押罪犯(题解)](http://www.mshxw.com/aiimages/31/856373.png)
