[NOIP2010]关押罪犯(题解)

目录

篛箕的自诉

题目分析+代码:

        第一种做法(图论+搜索):

        第二种做法(并查集,拓展域):


篛箕的自诉

        以前的我对你爱搭不理,现在的我对你笔不能提:

传送门

        以前学习搜索的时候看到这个题就感觉一定会做,对他爱搭不理。到现在回过头来看的时候,却发现自己已经忘记怎么做了(自己真是个弱鸡)。

题目分析+代码:

        第一种做法(图论+搜索):

        这个题给了我们一个图(不会图论的可以看下一种方法),对于每个节点的边权,我们可以将这个图分成两份,使得这两个图的边权尽可能的小,求这个最小值的最大值;

        一想到最小值的最大值,这不就是二分答案吗?然而,问题的关键给到了cheak函数上,对于一个 mid ,我们要如何来确定这个mid的可行性?

        首先,对于一个图,我们把边权大于 mid 的节点分开到两个监狱里面,边权小于等于mid的就不需要判断了,然后判断是否能把这个图分成二分图(又是一个图论的知识),这就可以用上板子了,判断一个图能否二分的板子:

博主的二分图板子

        这样就顺理成章的写出了nlogn复杂度的二分加二分图的判定,代码如下:

#include <bits/stdc++.h>
#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("%d\n", r);
    
    return 0;
}

        第二种做法(并查集,拓展域):

        如果不会的可以先去看看这个题:食物链,很经典的并查集拓展域。

        对于一对罪犯,如果他们的仇恨值较大的话,我们当然想把他们拆开,这样得到的仇恨值肯定会相应变小,我们就用这样的贪心的思路,来拆开仇恨值较大的两对罪犯。当我们拆到不能再拆的时候,那么这一对罪犯的仇恨值就是最后的仇恨值。

        那么如何才能拆开一对罪犯呢?这时候就想到了并查集的扩展域,用一个域表示一个监狱,当我们拆分到不能拆分的时候输出这个仇恨值。

#include <bits/stdc++.h>
#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。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

あなたのことが好きです319

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

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

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

打赏作者

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

抵扣说明:

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

余额充值