关押罪犯 [并查集] [二分+二分图]

5 篇文章 0 订阅
1 篇文章 0 订阅

题目链接:传送门vijos

                                                                                                                                                        

题目描述:

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<=1000000000,且每对罪犯组合只出现一次。

                                                                                                                                                        

 

输出: 

输出文件共1行,为Z市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。

                                                                                                                                                          

样例输入:

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

                                                                                                                                                          

样例输出:

3512

                                                                                                                                                          

 题意:

很好懂,就是把N个犯人分配到2所监狱里面去,但某一些犯人之间会出现怨气值,

如果,可以把有矛盾的所有犯人分开到两所监狱当然是最好的啦。ans=0

但是,如果不行,那么只能把影响降到最低(所指的最低不是所有犯人之间的怨气值的总和),

而是两所监狱中最大的怨气值的两位大佬的影响。ans=分配完的两所监狱后,最大影响力的两位大佬怨气值。

一开始我误解为把所有犯人之间的怨气值的总和最小了。。。。

                                                                                                                                                          

题解:

我不会做,然后看了网上别人的做法才领悟了这道题的奥妙之处了。题目其实很明白就是给了两所监狱。

然后进行的操作是不断分配犯人。

  • 第一种方法:把两位大佬之间的关系进行建边,利用贪心的想法把边的权值进行从大到小地排序,然后通过并查集,把两位大佬分别分配到两个集合,具体怎么分呢?
  • 首先我们可以把最大矛盾的两位大佬分为两个监狱。首先我们可以看成 监狱1是A大佬统治,监狱2是B大佬统治。
  • 第二条边~第M条边历遍时,主要有三种情况:
  • 情况一:如果和两大联合没什么关系的,首先两个有怨气的两位暴躁老哥关系先暂存一下。
  • 情况二:如果发现某一方,和两大联合有怨气,那么把另一方和这个联合结合(敌人的敌人就是朋友—毛泽东)
  • 情况三:最后发现,原来联合之间居然有两个人有怨气,没办法维持了就输出这一个怨气值

 

 

尊重原创,以下代码参考这篇博客

#include<bits/stdc++.h>
using namespace std;
typedef struct Edge{
    int u,v,w;
}edge;
edge e[100500];
int pre[100500],tmp[100500];
int cmp(edge a,edge b){
    return a.w>b.w;
}
int find_father(int x){
    if(pre[x]==x) return x;
    pre[x]=find_father(pre[x]);
    return pre[x];
}
void Union(int x,int y){
    int fx=find_father(x),fy=find_father(y);
    if(fx!=fy){
        pre[fx]=fy;
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    }
    memset(pre,0,sizeof(pre));
    memset(tmp,0,sizeof(tmp));
    for(int i=1;i<=n;i++){
        pre[i]=i;
        tmp[i]=0;
    }
    sort(e+1,e+1+m,cmp);
    for(int i=1;i<=m;i++){
        int u=e[i].u;
        int v=e[i].v;
        int w=e[i].w;
        int fu=find_father(u);
        int fv=find_father(v);
        if(fu==fv){
            printf("%d\n",w);
            return 0;
        }
        if(tmp[u]==0){
            tmp[u]=v;
        }else{
            Union(v,tmp[u]);
        }
        if(tmp[v]==0){
            tmp[v]=u;
        }else{
            Union(u,tmp[v]);
        }
    }
    printf("0\n");
    return 0;
}

第二种方法:

不能再画图了,直接说就够明白了。 

我们可以建图,2×M条边的无向图。通过DFS进行染色。

怎么得到答案呢,我们可以通过二分查询来找答案。

因为我们知道,如果我们固定一组关系时,只要二分过程中的Mid,比他小的边可以忽略。

只要我们能够建出二分图,R=mid

如果建不出二分图,L=mid+1;

最后找出答案

尊重原创,以下代码参考这个博客

#include<bits/stdc++.h>
using namespace std;
const int N=20500;
const int M=100000;
typedef struct Edge{
    int to,next,w;
}edge;
edge e[M<<1];
int head[N],cnt,n,m,W,a,b,c,ans,w[M],S[N];
void add_edge(int u,int v,int w){
    e[cnt].to=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt++;
}
bool dfs(int u){
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;
        int w=e[i].w;
        if(w<=W) continue;
        if(S[u]==S[v])return false;
        if(!S[v]){
            S[v]=3-S[u];
            if(!dfs(v))return false;
        }
    }
    return true;
}
bool check(int x){
    for(int i=1;i<=n;i++){
         if(S[i]==0){
             S[i]=1;
             if(!dfs(i)) return false;
         }
    }
    return true;
}
int main()
{
      memset(head,-1,sizeof(head));
      scanf("%d%d",&n,&m);
      for(int i=1;i<=m;i++){
          scanf("%d%d%d",&a,&b,&c);
          add_edge(a,b,c);
          add_edge(b,a,c);
          w[i]=c;
      }
      sort(w+1,w+m+1);
      int L=1,R=m,mid;
      while(L<R){
          memset(S,0,sizeof(S));
          mid=(L+R)>>1;
          W=w[mid];
          if(check(mid)){ ans=w[mid], R=mid; }
          else L = mid+1;
      }
      if(L==1&&L>R){
          printf("%d\n",0);
      }else{
          printf("%d\n",ans);
      }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值