• 并查集的用法——维护关系


    加权维护集合/关系

    例题(中二...

     1 #include<cstdio>
     2 #include<algorithm>
     3 using namespace std;
     4 #define MAX 30000+99
     5 int t;
     6 int num[MAX], next[MAX];
     7 //num[i]表示i号战舰所处列中共有多少战舰, next[i] 表示i到所处列的第一个战舰中隔了有多少舰队 
     8 int fa[MAX];
     9 
    10 int find(int x) {
    11     if(x == fa[x]) return fa[x];
    12     int fax = find(fa[x]);
    13     next[x] += next[fa[x]];//在递归中将 x前面的战舰 和 x 的next值更新
    14     //想想这为什么不能是fax 
    15     return fa[x] = fax; //在此说明:不用担心x后面的战舰,因为若是输入到了他们,在find函数中会更新到的 
    16 } 
    17 
    18 int main() {
    19     for(int i = 1; i <= 30000; i++) fa[i] = i, num[i] = 1;
    20     scanf("%d",&t);
    21     char cmd;
    22     int x,y,fax,fay;
    23     for(int i = 1; i <= t; i++) {
    24         scanf("%s%d%d", &cmd,&x,&y);
    25         fax = find(x), fay = find(y);
    26         if(cmd == 'M') {
    27             next[fax] += num[fay];//此处都是对fa操作 
    28 //            next[x]+=num[y];
    29             num[fay] += num[fax];
    30             fa[fax] = fay;
    31             num[fax] = 0;//其实这里将fax列清0的操作不用写,因为不可能在往fax后面插入战舰了 
    32         }
    33         if(cmd == 'C') {
    34             if(fax != fay) printf("-1
    ");
    35             else {
    36                 printf("%d
    ", abs(next[x]-next[y])-1);//注意 
    37             }
    38         }
    39     }
    40     return 0;
    41 }

     

    进阶题(其实博主解释的也不是很清楚,读者们可以自行查阅

    食物链:
    value[i] 表示 i到根结点的距离(0为同类,1表示根吃i,2表示i吃根)(可看做深度),保证根在最上方 
    判断:    若x,y的根节点相同,则表示x,y确定过关系,这时,只需要判断它说的是不是对的
        若x,y的根节点不同,则表示x,y没有确定关系(确定关系了的根节点都是同一个 ),按照题意(前面无语句与他相违背),为真话
    把好多种(不多吧)情况列出来之后,防止为负的,还防止超界,所以下面的用 (。。。+3)%3

    1. a == 1时 给出的x与y是同类
      1) 若(根节点相同(fx == fy)): 判断
      :   if(x,y到他们根结点的距离不同(v[x] != v[y])),则自相矛盾 ,不成立,ans++。
        else 则成立 。
      2) 若(根节点不相同(fx != fy))
      /x与y之间没有边相连(fx!=fy) 即表示在这之前没有确定过x,y之间的关系
      所以这条语句为真,并合并根节点(注意这里39行是以fy为根)
      :   fa[fx] = fy;
        value[fx] = (value[y] - value[x] + 3) % 3 //注: 此时x,y 处在同一深度//这的value[x]是x到fx的距离
        //防止为负的


    2. a == 2时 给出的x吃y
      1) 若(根节点相同(fx == fy)): 判断
      //这时给出的x吃y为真话时,满足value[y] - value[x] = 1
      :   if( (value[y])%3 != (value[x]+1)%3) ,为假,ans++
        else 则成立

      2) 若(根节点不相同(fx != fy)):
      同上,没有确定关系,为真话,并合并根节点,确定关系(根节点以fy)
      :   fa[fx] =fy;
        value[fx] = (value[y]- 1 -value[x] + 3) % 3

     1 #include<cstdio>
     2 const int MAX = 500000+9; 
     3 
     4 int n,k,a,x,y,ans;
     5 int fa[MAX],val[MAX];
     6 
     7 int dad(int x)
     8 {
     9     if(fa[x]!=x)
    10     {
    11         int fx=fa[x];
    12         //注意:要先写fa[x] = father(fa[x]) ,因为还要将val更新一下,这个需要一直找父亲 
    13         fa[x]=dad(fa[x]);
    14         val[x]=(val[x]+val[fx])%3;
    15         return fa[x];
    16     }
    17     return fa[x];
    18 }
    19 
    20 int main() {
    21     scanf("%d%d",&n,&k);
    22     for(int i = 1; i <= n; i++) fa[i] = i;
    23     for(int i = 1; i <= k; i++) {
    24         scanf("%d%d%d",&a,&x,&y);
    25         if(x>n || y>n) {
    26             ans++;
    27             continue;
    28         }
    29         int fx = dad(x), fy = dad(y);
    30         if(a == 1) {
    31             if(fx == fy) {
    32                 if(val[x] != val[y]) ans++;
    33             }
    34             else {
    35                 fa[fx] = fy;
    36                 val[fx] = (val[y] - val[x]+3)%3;
    37             }
    38         }
    39         else {//x 吃 y 
    40             if(fx == fy) {
    41                 if((val[x] + 1) % 3 != (val[y]) % 3 ) 
    42                 ans++;
    43             }
    44             else {
    45                 fa[fx] = fy;
    46                 val[fx] = (val[y] - val[x] - 1 + 3)%3;
    47             }
    48         }
    49     }
    50     printf("%d",ans);
    51 }

    其他题目:

    ps: 最近并查集的题目都没有找到测评网站(尤其是jzoj的题目..)若写错,见谅,望提醒

     题目大意

    观众席每一行构成一个圆形,每个圆形由300个座位组成,对300个座位按照顺时针编号1到300,且可以认为有无数多行。现在比赛的组织者希望 观众进入场地的顺序可以更加的有趣,门票上规定的不是每个人的座位, 而是与这个圈中某个人的相对位置,可以坐在任意一行。 门票上标示的形式如下:A B x 表示第B个人必须在A的顺时针方向x个位置(比如A坐在4号位子,x=2,则B必须坐在6号位子)。 现在你就作为志愿者在 入场口检票。如果拿到一张门票,与之前给定的矛盾,则被视为是假票, 如果无矛盾,视为真票。现在给定该行入场观众的顺序,以及他们手中的 门票,请问其中有多少假票?

    数据范围
    1<=n<=50,000,1<=m<=100,000


    样例:

    10 10
    1 2 150
    3 4 200
    1 5 270
    2 6 200
    6 5 80
    4 7 150
    8 9 100
    4 8 50
    1 7 100
    9 2 100

     分析:

    因为要判断是否与之前的产生矛盾,想到了用并查集
    注意: 两个人是可以坐同一个座位的(因为有无线个圈),即你只要判断一个人的相对位置是否改变即可
    不用判断两个人是否在同一座位上
    加权维护(距离)关系

    #include<cstdio>
    #define MAX 50000+99 
    int fa[MAX];
    int dis[MAX];//dis[i] 表示i到祖先节点的相对距离
    int n,m,ans;
    
    int find(int x) {
        if(x == fa[x]) return fa[x];
        int fax = find(fa[x]);
        dis[x] = (dis[fa[x]] + dis[fax])%300;
        return fa[x] = fax;
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n; i++) fa[i] = i;
        int fax,fay,x,y,z;
        for(int i = 1; i <= m; i++) {
            scanf("%d%d%d",&x,&y,&z);
            fax = find(x), fay = find(y);
            if(fax != fay) {//这都是操控fa(因为find函数 
                fa[fax] = fay;//加入集合 (因为fay是祖宗,下面修改dis[fax]
                dis[fax] = (dis[x]+z-dis[y]+300)%300;//注意防负
            } else 
                if(dis[fax] != (dis[x]+z-dis[y]+300)%300) ans++;
        }
        printf("%d",ans);
    } 

    非加权维护集合/关系(扩展域维护?

    例题: 关押罪犯

    /*
    关押罪犯://维护集合
    问把N个人都关到这两个房间里会产生的最小的最大冲突,影响力为多少

    有贪心的思想: 希望最大的怒气值最小,就将怒气值由大到小排序,将怒气值大的拆开
    分别放入两个房间(分的两个集合), 再!!记录某人的敌人的代表元素!!
    如果由大到小遍历得到的两个人的敌人在同一个集合,那么这两个人的怒气值即为ans
    (因为必须把他们放在同一个房间(集合),如果不放在同一个房间,那么影响力反而会增大

    */

     1 #include<cstdio>
     2 #include<algorithm>
     3 using namespace std;
     4 const int MAXN = 20000+9;
     5 const int MAXM = 100000+9;
     6 
     7 int n,m;
     8 int fa[MAXN],dad[MAXN];
     9 
    10 int father(int x) {
    11     if(x == fa[x]) return x;
    12     return fa[x] = father(fa[x]);
    13 }
    14 
    15 struct node{
    16     int x,y,val;
    17     bool operator < (const node &xx) const {
    18         return val > xx.val ;
    19     }
    20 }e[MAXM];
    21 
    22 int main() {
    23     scanf("%d%d",&n,&m);
    24     for(int i = 1; i <= m; i++) {
    25         scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].val );
    26     }
    27     sort(e+1,e+1+m);
    28 //    for(int i = 1; i <= m; i++) {
    29 //        printf("%d : %d  %d  %d
    ",i,e[i].x ,e[i].y ,e[i].val );
    30 //    }
    31     for(int i = 1; i <= n; i++) {
    32         fa[i] = i;
    33     }
    34     
    35     for(int i = 1; i <= m+1; i++) {//i 循环到 m + 1 时便无冲突,会输出0 
    36         
    37         int fx = father(e[i].x ) , fy = father(e[i].y );
    38         int x = e[i].x , y = e[i].y ;
    39         if(fa[fx] == fa[fy]) {
    40             printf("%d",e[i].val );
    41             return 0;
    42         }
    43         else {//需要分开 
    44             if(!dad[x]) {//标记敌人 
    45                 dad[x] = y;
    46             }
    47             else {//将dad[x]与 dad[x]的敌人x的敌人y 合并 
    48                 fa[father(dad[x])] = fy;
    49             }
    50             if(!dad[y]) {//标记敌人 
    51                 dad[y] = x;
    52             }
    53             else {//将dad[x]与 dad[x]的敌人x的敌人y 合并 
    54                 fa[father(dad[y])] = fx;
    55             }
    56         }
    57     }
    58 }

    例题2描述

    柴先森通知完了这N个人过来取礼品,但没有必要N个人全都过来,因为他们有的是要好的朋友,他们只会派一个人去代领,这N个人他们要么是朋友,要么是情敌。
    而且有一点是肯定的,就是:
    我朋友的朋友是我的朋友;
    我情敌的情敌也是我的朋友。
    现在柴先森得到了这N个人的关系信息,请你帮忙算出,最多会有几个人去取礼品?

    题意: 给出N个人物之间的M条关系,可能互为朋友或敌人,现在朋友的朋友互为朋友,敌人的敌人也互为朋友,互为朋友的人构成一 个朋友集团,问一共有多少个朋友集团。

    维护人物关系的典例

    错误代码 :

    #include<cstdio>
    #include<iostream>
    using namespace std;
    #define MAX 10000
    int fa[MAX],ene[MAX];
    int n,m;
    int vis[MAX]; 
    
    int find(int x) {
        return x == fa[x] ? fa[x] : fa[x] = find(fa[x]); 
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n; i++) fa[i] = i;
        int fax,fay,x,y;
        char g;
        for(int i = 1; i <= m; i++) {
            scanf(" %c %d %d",&g,&x,&y);//输入字符的时候还是用cin吧 这里加了空格了 
            fax = find(x), fay = find(y); 
            if(g == 'F') {
                fa[fax] = fay;
                continue;
            }
            if(g == 'E') {
                if(!ene[x]) {//标记敌人 
                    ene[x] = y;
                } else {//既然x有了敌人(即x所在集合有了敌人的代表元素),就将x的敌人 与 y合并 
                    fa[find(ene[x])] = fay;
                }
                if(!ene[y]) {//同上 
                    ene[y] = x;
                } else {
                    fa[find(ene[y])] = fax;
                }
            }
        }
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            fax = fa[i];
            if(!vis[fax]) {
                ans++;
                vis[fax] = 1;
            }
        } 
        printf("%d",ans);
        return 0;
    }
    /*
    6 4
    E 1 4
    F 3 5
    F 4 6
    E 1 2
    */

    大佬解法(倍增并查集

    倍增并查集裸题,将一伙人放到一个集合,a的敌人的编号为a+n,当两个人是敌人时,将双方放到对方敌人的集合里。然后查询1~N所属的集合中有多少种不同的集合
    至于为什么能用倍增并查集呢???原因是情敌的情敌是我的朋友。这样子的关系就可以倍增了。如果并不知道情敌的情敌是不是我的朋友的话就不能用这个方法了。

    #include<cstdio>
    #define MAX 10000*2
     
    int n,m;
    int fa[MAX];
    int vis[MAX];
    
    int find(int x) {
        return x == fa[x] ? fa[x] : fa[x] = find(fa[x]);
    }
    
    int main() {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n<<1; i++) fa[i] = i;
        char fax,fay,g,a,b; 
        for(int i = 1; i <= m; i++) {
            scanf(" %c%d%d",&g, &a, &b);
            if(g == 'F') {
                fax = find(a), fay = find(b);
                fa[fax] = fay;
            } else {
                fax = find(a), fay = find(b+n);
                fa[fax] = fay;
                fax = find(a+n), fay = find(b);
                fa[fax] = fay;
            }
        }
        int ans = 0;
        for(int i = 1; i <= n; i++) vis[find(i)] = 1;
        for(int i = 1; i <= (n<<1); i++)
            if(vis[i]) {
                ans++;
            }
        printf("%d",ans);
    }
  • 相关阅读:
    Junit研究心得——准备研究Junit
    Nginx proxying for Tomcat applications
    将工厂模式升华为插件式框架【文摘】
    JIRA中输入验证时,将验证错误“InvalidInputException”写到对应字段顶部,而不是页面顶部。
    Junit研究心得——Junit中的Annotation
    Junit研究心得——Junit中的约定规则
    Java 编程的动态性【文摘】
    插件式设计开发心得(一)【文摘】
    Java SE 6 新特性: Instrumentation 新功能
    php实现首页自动选择语言跳转
  • 原文地址:https://www.cnblogs.com/tyner/p/10801970.html
Copyright © 2020-2023  润新知