NOIP 2010 提高组 关押罪犯

思路: 并查集 + 优先队列

优先队列貌似也用不上,排序就可以了。

样例输入

2 1

1 2 28135

样例输出

0

没有通过: 再次提醒我,注意边界。注意极端情况

刚开始,往二分图上想了

即将一个无向图分成两个独立的点集,使两个点集中所有的边的边权的最大值的最小值是多少(然而我不会二分图染色……)

扫盲: 什么叫做二分图,二分图的最大匹配
http://www.renfei.org/blog/bipartite-matching.html

二分图的最大匹配如果换一个说法:最多有多少互相喜欢的男孩/女孩可以配对儿?这就是最大匹配问题。

/*
输入
4 4 
1 2 100000
3 4 100000
1 3 2 
2 4 1 
输出
0
*/

#include <iostream>
#include <bits/stdc++.h>

using namespace std;
const int MM=20001;


int N,M; // N 罪犯数目   M 仇恨对数

struct  Hen{
      int jia;
      int yi;
      int cost;
      bool operator < ( const Hen &another) const {
            if(  cost >= another.cost  ) return true;
            else  return  false;
      }
};


int father[MM];
int rank[MM];
Hen hens[100001];

int  find(int x){
      if( father[x] == x ) return x;
      else {
           return father[x] = find( father[x] );
      }
}

void unite(int x,int y){
      int genx=find(x);
      int geny=find(y);
      if( genx != geny ) {
            if( rank[genx] > rank[geny] ){
                  father[geny] = genx;
            }
            else  {
                  father[genx] = geny;
                  if(rank[genx]==rank[geny]){ rank[geny]++ ;}
            }
      }
}

bool same(int x ,int y){
      return ( find(x) == find(y) );
}

int main()
{
      cin >> N >> M;
      for(int i=0;i<=N ;i++){   //注意这里的边界,考场中哪有这么都样例让你试呢。

            father[i]=i;
            rank[i] = 1;
      }

     // priority_queue<Hen>  pq;

      for(int i=1;i<=M;i++){
            int a ,b ,c; cin >> a >> b >>c;
            //pq.push( (Hen){a,b,c} );
            hens[i]=(Hen){a,b,c};
      }

      //排序就可以了!
      sort(hens+1 , hens+M+1);

      int result=0;
      /*while(!pq.empty()){

            Hen k =  pq.top();  pq.pop();
            if(  same(k.jia, k.yi) ){
                  result =  k.cost;
                  break;
            }
            else{
                  unite( k.jia, k.yi );
            }

      }*/

      for(int i=1;i<=M;i++){

            Hen k = hens[i] ;
            if(  same( k.jia, k.yi ) ){
                  result =  k.cost;
                  break;
            }
            else{
                  unite( k.jia, k.yi );
            }

      }
      cout << result;

      return 0;
}
只得了60分。苦苦思考。

在网上找到了答案:

/*
输入
4 4 
1 2 100000
3 4 100000
1 3 2 
2 4 1 
输出
0
*/

我的代码的结果是1  

网上找到了答案: 摘录如下:

第一个点集:1、4;第二个点集:2、3。这样,就不会产生任何事件,输出为0,但是,用刚才的思路会输出1。为什么呢?让我们来模拟一下。首先,让1、2在同一并查集;然后,让3、4在同一并查集;之后,让1、3在同一并查集,此时两个并查集合并。最后,再判断2、4时,便会得到两数在同一并查集,于是就输出了1。那么,这个错误就得到一个解释了:因为2、4的距离是奇数,所以即使它们同在一个并查集集合中,它们也不一定同在一个监狱。所以,距离问题也是一个需要考虑的问题。

似乎只能暴力维护了,但是这样又很可能TLE。那怎么办呢?这时,我们就要从前一个思路所产生的错误着手了。它产生错误的原因是:不能维护两点之间的距离。那么,我们可以引入一个新的节点(或者思路):补集。我们设置另外一个节点系统:i+n。凡是与i+n节点在同一个并查集的,都是不能与i在同一个集合里。这样,因为i+n这个节点世界上是不存在的,所以保证了每一个并查集集合中的存在的点之间的距离都是偶数。这样就保证了一旦两个点在同一个集合,则它们必须在同一个点集中。另外,注意对i+n也要压缩路径,因为有可能有两个补集节点相连,这时如果不find就会造成一大堆东西连在一起,就会MLE(具体怎么搞得我也不知道)。

然后记得数组要开2*n,然后用一个优先队列维护一下怒气值,就搞定了。

其实代码很简单,但是思路还是很巧妙的。还有一些细节的东西(也没多少),具体请见代码:完美全部通过落谷数据

/*
输入
4 4
1 2 100000
3 4 100000
1 3 2
2 4 1
输出
0
*/

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int MM=40002;
int N,M; // N 罪犯数目   M 仇恨对数
struct  Hen{
      int jia;      int yi;      int cost;
      bool operator < ( const Hen &another) const {
            if(  cost >= another.cost  ) return true;
            else  return  false;
      }
};


int father[MM];
int rank[MM];
Hen hens[100001];

int  find(int x){
      if( father[x] == x ) return x;
      else {
           return father[x] = find( father[x] );
      }
}

void unite(int x,int y){
      int genx=find(x);
      int geny=find(y);
      if( genx != geny ) {
            if( rank[genx] > rank[geny] ){
                  father[geny] = genx;
            }
            else  {
                  father[genx] = geny;
                  if(rank[genx]==rank[geny]){ rank[geny]++ ;}
            }
      }
}

bool same(int x ,int y){
      return ( find(x) == find(y) );
}

int main()
{
      cin >> N >> M;
      for(int i=0;i<N*2 ;i++){   //注意这里的边界,考场中哪有这么都样例让你试呢。
            father[i]=i;
            rank[i] = 1;
      }

     // priority_queue<Hen>  pq;

      for(int i=1;i<=M;i++){
            int a ,b ,c; cin >> a >> b >>c;
            //pq.push( (Hen){a,b,c} );
            hens[i]=(Hen){a-1,b-1,c};   //转化成 0 到 n-1
      }

      //排序就可以了!
      sort(hens+1 , hens+M+1);

      int result=0;
      /*while(!pq.empty()){
            Hen k =  pq.top();  pq.pop();
            if(  same(k.jia, k.yi) ){
                  result =  k.cost;
                  break;
            }
            else{
                  unite( k.jia, k.yi );
            }
      }*/

      for(int i=1;i<=M;i++){
            Hen k = hens[i] ;
            if(  same( k.jia, k.yi )   ){
                  result =  k.cost;
                  break;
            }
            else{
                 unite( k.jia, k.yi + N );
                 unite( k.jia+N, k.yi );
            }

      }
      cout << result;
      return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值