P1525 关押罪犯

在这里插入图片描述

在这里插入图片描述

这道题看到好多dalao用的并查集,看了一下题解看不太懂,还是我太弱,只能用二分图+二分来做。
二分图就是将图中的点分成两边,也就是分成两个集合,使得。每个集合中的点之间不会有边相连接。如果有边相连一定是不同集合点相连,关于二分图的判定也很简单:就是看有没有奇数的环,从中我们也可以得到一个结论,没有边的图一定是二分图。

在这里插入图片描述
这就是二分图,看到没,图中两个集合的内部没有边,只有不同集合之间才有边相连接。关于二分图的判定,这里介绍一种方法:~染色法,只有两种颜色:红色和蓝色

我们已一个顶点开始,任何一个顶点都可以。然后将其染成红色,然后遍历其所与之相连的点,如果其是红色,则图不是二分图,直接返回false,如果是蓝色则跳过,如果没有染色,则把这个点染成和与之相连的那个点相反的颜色(蓝色),然后从这个点出发依次遍历其他与之相连的点~一般用dfs实现,代码也很好写。如果遍历的过程中没有返回false,则最后返回true

最理想的情况当然是没有冲突,题目好像有一个样例是没有冲突的。我们可以这样,假设我们找到了一个mid使得这个两个监狱中冲突值最小为mid ,很显然,对于mid较小的时的方案,当mid较大时依然可行,所以这就符合单调性,可以用二分来求解~

我们先设left=0,right为罪犯里面两个罪犯之间的怨气的最大值,然后开始二分.

int mid=(left+right)/2,将此时怨气值大于mid的点连接起来,也就是说这两个罪犯关在不同的监狱,那么小于等于mid的点就不连接,这两个罪犯在一个监狱。这样连接以后,会形成一个图,我们判断这个图是否是二分图.这个图可能是连通图,可能不连通,如果是连通图,我们只需要dfs一次即可,但是图是非连通的我们需要dfs N次(N为顶点的个数),如果这个图是二分图,那么我们就看看还有没有比mid更小的怨气值成立也可以是二分图(right=mid-1),如果不是二分图,说明此时怨气值mid太理想化了,不满足条件,需要看看怨气值大一点是否满足条件,所以就令left=mid+1.

在这里插入图片描述
样例的最终答案就是这样的,把大于3512的怨气值连起来。然后判断是否是二分图.很显然是二分图,执行到最后一步算法也就结束了right=mid-1.此时left>right

记住上面是将>mid的连接,不是>=mid的连接,如果是大于等于mid的连接,最终结果要减去1,为什么呢,你仔细再看看上面那个图,等于3512的部分不要连接.

刚开始我也是把大于等于mid的连接,调试后才发现错了.
在这里插入图片描述
这是倒数第二步调试,可以看到此时要将大于等于3513的部分连接起来,此时满足二分图.所以顺便保存一下结果res=mid=3513

然后right=mid-1=3512
在这里插入图片描述
mid=(left+right)/2=3512,此时将大于等于3512部分连接,不能得到二分图,此时left=mid+1=3513>right,所以不满足条件了,那么我们的res就是3513,要减去1才是最终结果.但是有一种情况减1也是错的,如果此时的阵容可以满足怨气值为0,再减去1,那就会出错了.就比如下面这样

在这里插入图片描述

所以我们还是换一种安全的做法,当大于mid的时候,我们才连接两个罪犯.这样上面样例最后一步就是mid=3512,此时连接怨气值大于3512的两个罪犯,然后判断其是否是二分图.满组条件.right=mid-1<left,满足条件退出循环~

下面是AC代码~

#include<iostream>
#include <vector>
#include <cstring>
using namespace std;
#define  Max 100000
struct Node
{
    int v2;
    int weight;
};
vector<Node> graph[Max];
vector<Node> g[Max];
int Max_val=-1;
int color[Max];
bool solve(int N);
bool dfs(int v,int col);
int res=0;
    int main()
    {
        int N,M;
        cin>>N>>M;
        int v1,v2,val;
        for(int i=1;i<=M;i++)
        {
            cin>>v1>>v2>>val;
            Node node{v2,val};
            graph[v1].push_back(node);
            Max_val=max(Max_val,val);
        }

        int left=0,right=Max_val;
        while(left<=right)
        {
            //假设此时mid是最大的二分边界
            int mid=(left+right)/2;
            for(int i=1;i<=N;i++)
            {
                g[i].clear();
            }
            for(int i=1;i<=N;i++)
            {
                if(graph[i].size()!=0)
                {
                    for(int j=0;j<graph[i].size();j++)
                    {
                        if(graph[i][j].weight>mid)//大于mid的罪犯之间连一条边
                        {
                            Node node{graph[i][j].v2,graph[i][j].weight};
                            g[i].push_back(node);
                            Node nodes{i,graph[i][j].weight};
                            g[graph[i][j].v2].push_back(nodes);
                        }
                    }
                }
            }
            memset(color,0, sizeof(color));
            if(solve(N))
            {
                right=mid-1;
                res=mid;
            }
            else{
                left=mid+1;
            }

        }
        cout<<res<<endl;
        return 0;
    }

    bool dfs(int v,int col)
    {
        color[v]=col;
        for(int i=0;i<g[v].size();i++)
        {
            if(color[g[v][i].v2]==col)//染上了相同的颜色
            {
                return false;
            }
            if(color[g[v][i].v2]==0&&!dfs(g[v][i].v2,-col)) //没有涂色,并且以v2为起点染色失败
            {
                return false;
            }
        }
        return true;
    }

    bool solve(int N) //判断是否是二分图
    {
        for(int i=1;i<=N;i++) //以i为顶点开始染色
        {
            if(color[i]==0) //没有被染色
            {
               if (!dfs(i,2)) //开始以i为顶点染色 成2
               {
                    return false;
               }
            }
        }
        return true;
    }


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值