codevs 1069 关押罪犯 (并查集)

题意:有n名罪犯,m个关系,每个关系(u, v, w)表示犯人u和v如果关在同一个监狱之间会产生怨气值w,现在有两个监狱,问你怎么放置着n个犯人,犯人之间最大怨气值最小,求这个值。N≤ 20000,M≤ 100000


思路:(点击打开链接)维护罪犯在哪一个监狱不方便,我们可以维护某两个罪犯是不是在一个监狱。考虑到并查集的本职工作是维护某两点在一个集合,不能很好地处理不在一个集合的情况,通过保存某个点的“敌人”集合来代表和他不在一个监狱的罪犯,间接地实现维护某两点不在一个集合的情况。在加入关系的时候进行判断,如果某两点已经在一个集合,说明他们无论如何也安排不到不同的两个监狱了,输出仇恨值即可;如果不在一个集合,就将犯人和对方的敌人合并到一个集合。

(敌人的敌人就是朋友)


代码:

#include
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
struct node
{
    int u, v, w;
    bool operator <(const node &a) const
    {
        return w > a.w;
    }
}a[maxn];
int pre[maxn], en[maxn], n, m;

int Find(int x)
{
    int r = x;
    while(pre[r] != r) r = pre[r];
    int i = x, j;
    while(i != r)
    {
        j = pre[i];
        pre[i] = r;
        i = j;
    }
    return r;
}

void join(int x, int y)
{
    int a = Find(x);
    int b = Find(y);
    if(a != b)
        pre[b] = a;
}

int main(void)
{
    while(cin >> n >> m)
    {
        for(int i = 1; i <= n; i++)
            pre[i] = i, en[i] = 0;
        for(int i = 1; i <= m; i++)
            scanf("%d%d%d", &a[i].u, &a[i].v, &a[i].w);
        sort(a+1, a+1+m);
        int ans = 0;
        for(int i = 1; i <= m; i++)
        {
            int x = a[i].u;
            int y = a[i].v;
            int fa = Find(x);
            int fb = Find(y);
            if(fa == fb)
            {
                ans = a[i].w;
                break;
            }
            if(en[x]) join(y, en[x]);
            else en[x] = y;
            if(en[y]) join(x, en[y]);
            else en[y] = x;
        }
        printf("%d\n", ans);
    }
    return 0;
}


  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. using namespace std;  
  6. struct node  
  7. {  
  8.     int a , b , c;  
  9. };  
  10. const int N = 100000 + 50;  
  11. node man[N];  
  12. int p[N];  
  13. bool cmp(node a , node b)  
  14. {  
  15.     return a.c > b.c;  
  16. }  
  17. int find(int x)  
  18. {  
  19.     return x == p[x]?x:x=find(p[x]);  
  20. }  
  21. int main(int argc, char const *argv[])  
  22. {  
  23.     int n , m;  
  24.     cin >> n >> m;  
  25.     for (int i = 0; i < m; ++i)  
  26.         scanf("%d%d%d", &man[i].a, &man[i].b, &man[i].c);  
  27.     for (int i = 1; i <= 2*n; ++i)  
  28.     {  
  29.         p[i] = i;  
  30.     }  
  31.     sort(man , man + m , cmp);  
  32.     for (int i = 0; i < m; ++i)  
  33.     {  
  34.         int x = find(man[i].a); int y = find(man[i].b);  
  35.         if(x == y)  
  36.         {  
  37.             cout << man[i].c << endl;  
  38.             return 0;  
  39.         }  
  40.         p[x] = find(man[i].b + n);  
  41.         p[y] = find(man[i].a + n);  
  42.     }  
  43.     printf("0\n");  
  44.     return 0;  
  45. }  

用并查集表示每个犯人之间的关系,在同一个集合中则说明两人在同一个监狱,反之则不在同一个监狱。

用类似于克鲁斯卡尔的方法,先将边排序,然后按照仇恨值的大小从大到小处理。对于每一组,先看两个人能不能加入不同的集合,
如果可以,将两人加入不同的集合,如果不可以,则输出该组仇恨值。
用补集来表示两个点不在一个集合中。如a和b'在同一个集合中,则a和b不在同一个集合中,不能把b直接加入另一个集合,因为会对后面的结果产生影响。
如,3和4不在同一个集合中,但是我们现在不知道究竟是3在第一个监狱还是4在第一个监狱,所以此时用3和4‘在同一个集合中来表示3和4不在同一个集合中。


你可能感兴趣的:(并查集)