并查集 poj 1611 2524 1182

来源:互联网 发布:ipadpro10.5必备软件 编辑:程序博客网 时间:2024/06/06 00:11

先看一道水题

http://poj.org/problem?id=1611

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  *                             poj_1161.c *                            Disjoint Sets * 解题思路:使用并查集,每个节点关联一个元素个数num。初始:每一个元素单 * 独作为一个集合;然后读入每一行的学生,将每一行第一个学生和该行后面的 * 学生进行求并。最后返回元素0所在的集合的元素个数。 * 注意:查找时采用路径压缩,只有根节点关联的元素个数才是实际有效的。 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */#include <stdio.h>#define N 30000int father[N];int num[N];void init (int n){    int i;    for (i = 0; i < n; i++){        father[i] = i;        num[i] = 1;    }}int find (int v){return father[v] == v ? v : (father[v] = find(father[v])); /* 路径压缩 */}void merge (int v1, int v2){    int p1 = find (v1), p2 = find (v2);    if (! (p1 == p2)){        father[p2] = p1;        num[p1] += num[p2];    }}void poj_1182 (){    int n, m; /* number of students and number of groups */    int gn; /* number of students in a group */    int v1, v2;    int i, j;#ifdef INPUT    freopen ("d.txt", "r", stdin);#endif    while (scanf ("%d%d", &n, &m)){        if (0 == n && 0 == m){            break;        }        init (n); /* initialization */        for (i = 0; i < m; i++){            scanf ("%d%d", &gn, &v1);            for (j = 1; j < gn; j++){                scanf ("%d", &v2);                merge (v1, v2);            }        }        printf ("%d\n", num[find(0)]); /* 返回元素0所在的集合的元素个数 */    }#ifdef INPUT    fclose (stdin);#endif}int main(){    poj_1182 ();    return 0;}


再看水题 POJ 2524

http://poj.org/problem?id=2524

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  *                        poj_2524.c *                        并查集问题 * 解题思路:建立并查集,依次扫描,最后返回集合的个数。 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <stdio.h>#define N 50000int father[N];int depth[N]; /* 记录从该节点到最深的叶子节点的深度 */void init (int n){    int i;    for (i = 0; i < n; i++){        father[i] = i;        depth[i] = 0;    }}int find (int v){return father[v] == v ? v : (find(father[v]));}void merge (int v1, int v2){      /* 路径压缩 */    int p1 = find (v1), p2 = find (v2);    if (! (p1 == p2)){        if (depth[p2] < depth[p1]){            father[p2] = p1;        }        else if (depth[p2] > depth[p1]){            father[p1] = p2;        }        else{            father[p2] = p1;            depth[p1] ++;        }    }}void poj_2524 (){    int n, m; /* number of students and number of groups */    int v1, v2;    int i, j;    int ret;#ifdef INPUT    freopen ("d.txt", "r", stdin);#endif    j = 0;    while (scanf ("%d%d", &n, &m)){        if (0 == n && 0 == m){            break;        }        init (n);         ret = 0;        for (i = 0; i < m; i++){            scanf ("%d%d", &v1, &v2);            merge (v1, v2);        }        for (i = 0; i < n; i++){ /* 返回集合的个数 */            if (i == father[i]){                ret ++;            }        }        printf ("Case %d: %d\n", ++j, ret); /* 返回元素0所在的集合的元素个数 */    }#ifdef INPUT    fclose (stdin);#endif}int main(){    poj_2524 ();    return 0;}

比较难一点的经典题目:食物链问题

http://poj.org/problem?id=1182

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *   *                                     POJ_1182.c *   *                                 并查集解决食物链问题 *   *  copyright @ dnxiaochou@gmail.com *  解题思路: *  建立并查集。每个节点关联了父亲节点和一个权值。权值用于指示该节点和其父亲节点的关系(同类、被父亲所吃还是吃其父亲)。例如,假设 *  节点 X 是节点 Y 的父亲节点,节点 Y 的权值可以理解为: *    节点Y的权值         含义 *         0             Y和X属同类 *         1             Y被X吃 *         2             Y吃X *   *  那么数据结构可以定义为: *  #define N 100000 *  int father[N]; *  int relatcion[N]; *   *  初始化: *  每个节点是一个单独的集合,即每个节点的父亲节点都是自己,而权值均为0(自己同自己属同类)。 *  void init (int n){ *      int i; *      for (i = 0; i < n; i++){ *          father[i] = i; *          relation[i] = 0; *      } *  } *   *  查找树根: *  递归查找父亲节点的树根即可。不过,我们这里使用了路径压缩,压缩路径的时候如何更新权值呢? *  void find (int v){ *      int x = father[v]; *      if ( x == v){ *          return x; *      }  *      father[v] = find (x); 路径压缩       *      relation[v] = (relation[v] + relation[x]) % 3; 思考:这条语句如何得来? *      return father[v];     *  } *  我们仔细分析一下上面的语句是如何得到的? *  亦即给定Z是Y的父亲含义下的relation[Y]和Y是X的父亲含义下的relation[X],我们看看如何构造Z是X的父亲含义下的relation[X],不难枚举 *  如下的情况。 *    Z是Y的父亲含义下的relation[Y]   Y是X的父亲含义下的relation[X]   Z是X的父亲含义下的relation[X] *                0                                0                              0   *                0                                1                              1   *                0                                2                              2   *                1                                0                              1   *                1                                1                              2   *                1                                2                              0   *                2                                0                              2   *                2                                1                              0  *                2                                2                              1 *  那么我们看到,在路径压缩的时候,很容易根据中间人Y,把Y的孩子提升作为Y的父亲的孩子。同时更新权值即可。容易得出更新的操作为: *   *                               relation[X] = (relation[Y] + relation[X]) MOD 3 *   *  即知道爷爷同父亲的关系和父亲同儿子的关系,可以通过上面的公式计算爷爷和儿子的关系(当考虑重新设置儿子的父亲节点时)。 *  同理,容易得到,Y是X的父亲意义下的X的权值 同 X是Y的父亲意义下的Y的权值的关系为:二者的和为3。 *  举例: *    Y是X的父亲含义下的relation[X]   X是Y的父亲含义下的relation[Y]                     结论 *                0                                0                 relation[Y] = (3 - relation[X]) MOD 3 *                1                                2                 relation[Y] = (3 - relation[X]) MOD 3 *                2                                1                 relation[Y] = (3 - relation[X]) MOD 3 *  即 *  合并操作: *  首先谈一下,我们每次执行查找操作都将极大地减少树的高度(叶子节点的高度为0),为什么? *  具体的情况为:初始化后每个节点是一棵树,这时候每棵树的高度均为0。然后程序循环读入数据,并执行合并操作,在合并操作之前,我们先调用 *  了查找操作,查找操作则执行了路径压缩,将从v到树根的之间所有的中间节点(包括v)的父亲节点都设置为树根节点。 *   *  如何执行合并操作呢? *  假设现在输入是X和Y以及他们的关系D(取值为1或2),那么先调用查找操作:A=find(X),B=find(Y)。 *  注意:find(X)执行路径压缩时使得A是树根也是X的父亲;find(Y)执行路径压缩时使得B是树根也是Y的父亲。 *  我们在每次合并操作中都将B的父亲节点设置为A。即parent[B] = A。接下来要思考的是:如何更新节点B的权值? *  注意到,X作为Y的的父亲意义下的Y的权值已经知道(就是输入的D减1),Y作为B的父亲意义下的B的权值为 (3-B作为Y的父亲意义下的Y的权值) MOD 3; *  那么,X作为B的父亲意义下的B的权值记为 *                                        ((D - 1) + (3 - relation[Y]) MOD 3) MOD 3 *  上式的分析是为了将B作为X的孩子,链接到X上去。那么接下来如何将B链接到A上去呢? *  分析是同样的,注意到find(X)执行路径压缩时使得A是树根也是X的父亲,现在已知A作为X的的父亲意义下的X的权值(relation[X]),X作为B的父亲 *  意义下的B的权值(上式),则A作为B的父亲意义下的B的权值为: *                                (relation[X] + ((D - 1) + (3 - relation[Y]) MOD 3) MOD 3) MOD 3 *  使用同余定理有: *                      relation[B] = (relation[X] + (D - 1) + (3 - relation[Y])) MOD 3 *   *  因此我们的合并操作中:完成了树的合并,和新子树的权值的设置。 *   *  如何判断是不是假话呢? *  输入的格式为 D X Y *  其中D的取值为1或2; *  那么, *  如果D=1,即输入表述X与Y属同类。若X与Y在一个集合中(这个判断势必调用了find(),而调用find()将会设置X和Y的父亲节点为树根节点),那么 *  如果输入为真话,则X同根节点的权值和Y同根节点的权值势必一样。即若relation[X] != relation[Y]就是假话;否则就是真话,执行合并操作; *  如果D=2,即输入表征X吃Y。若X与Y在一个集合中(这个判断势必调用了find(),而调用find()将会设置X和Y的父亲节点为树根节点),那么 *  如果输入为真,用A表示X和Y的树根,那么现在已知了A作为Y的父亲意义下Y的权值(relation[Y])和X作为A的父亲意义下的A的权值 *  ((3 - relation[X]) MOD 3),则X作为Y的父亲意义下的Y的权值为 *   *                            (relation[Y] + (3 - relation[X]) MOD 3) MOD 3 *   *  那么,只要上式等于1,则输入为真话,否则是假话;若果不在一个集合中,则执行合并操作。 *   *  仔细分析发现,上面的判断还可以用一个式子来合并: *  不管D是多少,只要 *                        (D - 1) 等于 (relation[Y] - relation[X] + 3) MOD 3 *   *  则就是真话,否则是假话。 *      *  具体的C代码如下: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * * * * * * * * * * * * * * * * * */   #include <stdio.h> #define N 50001  int father[N]; int relation[N];  static void init (int n); static int find (int v); static void merge (int v1, int v2, int p1, int p2, int d); static void poj_1182 ();   int main (int argc, char * argv[]){    poj_1182 ();    return 0; }  static void init (int n){    int i;    for (i = 1; i <= n; i++){        father[i] = i;        relation[i] = 0;    } }  static int find (int v){    int x = father[v];    if (x == v){        return v;    }    father[v] = find (x); /* 路径压缩 */    relation[v] = (relation[v] + relation[x]) % 3; /* 更新权值 */    return father[v];  }  static void merge (int v1, int v2, int p1, int p2, int d){    father[p2] = p1;    relation[p2] = ((3 - relation[v2]) + (d - 1) + relation[v1]) % 3; }  static void poj_1182 (){    int n, m;    int d;    int v1, v2;    int p1, p2;    int i;    int ret;#ifdef INPUT    freopen ("d.txt", "r", stdin);#endif         ret = 0;    scanf ("%d%d", &n, &m);    init (n);    for (i = 0; i < m; i++){        scanf ("%d%d%d", &d, &v1, &v2);        if (v1 > n || v2 > n){            ret ++;        }        else if (2 == d && v1 == v2){            ret ++;        }               else{            p1 = find (v1);            p2 = find (v2);            if (! (p1 == p2)){                merge (v1, v2, p1, p2, d);            }        else if ( d - 1 != (3 + relation[v2] - relation[v1]) % 3){                ret ++;            }        }    }    printf ("%d\n", ret);#ifdef INPUT    fclose (stdin);#endif }


原创粉丝点击