算法竞赛进阶指南 关押罪犯

首先说明我们要讲的知识点:

1:二分图不存在奇数环,染色法不矛盾:

2:匈牙利算法: 匹配,最大匹配,匹配点,增广路径

3:最小点覆盖,最大独立集,最小路径点覆盖,(最小路径重复点覆盖)。

      结论:最大匹配数 = 最小点覆盖 = 总点数 - 最大独立集 = 总点数 - 最小路径覆盖

首先我们先说:二分图 <---> 图中不存在奇数环 <--->染色法无矛盾这个结论有点明显,就不加证明了。。

例题:关押罪犯

来源:《算法竞赛进阶指南》, NOIP2010提高组
 

S 城现有两座监狱,一共关押着 N 名罪犯,编号分别为 1∼N。
 
他们之间的关系自然也极不和谐。
 
很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。
 
我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。
 
如果两名怨气值为 c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c 的冲突事件。
 
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。
 
公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
 
在详细考察了 N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。
 
他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。
 
假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
 
那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
 
输入格式
第一行为两个正整数 N 和 M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
 
接下来的 M 行每行为三个正整数 aj,bj,cj,表示 aj 号和 bj 号罪犯之间存在仇恨,其怨气值为 cj。
 
数据保证 1≤aj<bj<N,0<cj≤109 且每对罪犯组合只出现一次。
 
输出格式
输出共 1 行,为 Z 市长看到的那个冲突事件的影响力。
 
如果本年内监狱中未发生任何冲突事件,请输出 0。
 
数据范围
N≤20000,M≤100000
输入样例:
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
输出样例:
3512

解题思路:

这题要用到二分,不懂二分的可以看看这篇。

(16条消息) 《二分模板详解》_wsh1931的博客-CSDN博客

将罪犯之间的怒气值大于mid分到为一组,即二分的右边,将罪犯之间的怒气值小于等于mid的分到左边:最优解即为大于mid的最小值即最小的满足条件的解。

证明二分的两段性:任意选取二分右边一个大于mid的数ans, 因为mid可以满足条件,则因为ans > mid即所要连接的边更少,即要连接的边是当罪犯怒气值大于mid的子类,因此右边成立。

反证法:任取左边一个小于等于mid的数ans,若ans能成立即说明大于mid的第一个数不是最优解,与最优解矛盾所以不成立。


#include <cstdio>
#include <cstring>
#include <iostream>
 
using namespace std;
 
const int N = 20010, M = 200010;
 
int n, m;
int color[N];//表示N这个人是否被染色过这里我们假设1为白色,2为红色
int h[N], e[M], ne[M], w[M], idx;
 
void add(int a, int b, int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx ++ ;
}
 
bool dfs(int u, int c, int mid)
{
    color[u] = c;//将点u染成颜色c
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (w[i] <= mid) continue;//将大于mid的罪犯的怒气染色。
        if (!color[j])
        {
            if (!dfs(j, 3 - c, mid)) return false;//将u的子节点染成不同于u的颜色
            //若c = 1则3 - c = 2
            //若c = 2则3 - c = 1
        }
        else if (color[j] == c) return false;//若j已经被染色且颜色与u相同说明出现矛盾
                
    }
    
    return true;//排除了所有失败的结果。
}
 
bool check(int mid)
{
    memset(color, 0, sizeof color);//初始化
    
    for (int i = 1; i <= n; i ++ )
        if (!color[i]) //若 i 还未被染色
            if (!dfs(i, 1, mid)) return false;//若 i 染色失败
    return true;//排除了所有失败情况说明染色成功。
}
 
int main()
{
    cin >> 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)//该模板是找出满足条件的大于mid的最小值
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    
    cout << r << endl;
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

啥也不会hh

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

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

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

打赏作者

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

抵扣说明:

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

余额充值