并查集 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 }
- 并查集 poj 1611 2524 1182
- poj 2524 并查集
- 并查集 POJ 2524
- poj-2524 并查集
- poj 2524(并查集)
- POJ-2524 并查集
- poj 2524(并查集)
- POJ 2524 并查集
- POJ 2524 查并集
- POJ 2524 并查集
- POJ 2524 并查集
- poj 2524 并查集
- POJ 2524 并查集
- poj 2524[并查集]
- POJ 2524 (并查集)
- POJ 2524 并查集
- poj 2524 并查集
- 并查集 POj 2524
- C++获取CPU信息应用经验分享
- .net 文件操作移动复制删除等
- 锐捷笔试CountDownLatch
- http://www.cnblogs.com/zhwl/archive/2013/04/15/3022066.html
- 关于前端meta
- 并查集 poj 1611 2524 1182
- 大端小端
- java 简单冒泡排序
- ASP.NET上传文件的大小限制问题
- linux进程解析--进程切换
- 利用牛顿迭代法 求n次方根
- ABAP中SELECTION-SCREEN用法
- nginx 连接数
- vmware 安装ubuntu