poj1182(种类并查集好题)
不得不说,我得感谢@驱动幽灵百鬼夜行小肆,正是因为看明白了他给出的解析,我才完全弄懂种类并查集的,这里,我也不想去改其他的,就直接引用他的解题报告吧
转载:http://blog.csdn.net/c0de4fun/article/details/7318642
- /**
- Problem:1182 - 食物链,NOI2001
- Begin Time:4th/Mar/2012 1:00 p.m.
- End Time:4th/Mar/2012 6:47 p.m.
- Cost Time:两天多,看的别人的解题报告AC的
- Reference:http://apps.hi.baidu.com/share/detail/16059767
- 测试数据:
- http://poj.org/showmessage?message_id=93058
- 输出:
- 上方有
- 教训:
- WA一次,没搞清楚先更新父节点relation还是更新当前节点relation的关系!!!
- (在最后那条犯错误了!)
- 思路:
- 老子决心要写一个,关于这道题的,最详细的解题报告。
- 本题思路是带权并查集,我们从最开始讲起。
- Part I - 权值(relation)的确定。
- 我们根据题意,森林中有3种动物。A吃B,B吃C,C吃A。
- 我们还要使用并查集,那么,我们就以动物之间的关系来作为并查集每个节点的
- 权值。
- 注意,我们不知道所给的动物(题目说了,输入只给编号)所属的种类。
- 所以,我们可以用动物之间“相对”的关系来确定一个并查集。
- 0 - 这个节点与它的父节点是同类
- 1 - 这个节点被它的父节点吃
- 2 - 这个节点吃它的父节点。
- 注意,这个0,1,2所代表的意义不是随便制定的,我们看题目中的要求。
- 说话的时候,第一个数字(下文中,设为d)指定了后面两种动物的关系:
- 1 - X与Y同类
- 2 - X吃Y
- 我们注意到,当 d = 1的时候,( d - 1 ) = 0,也就是我们制定的意义
- 当 d = 2的时候,( d - 1 ) = 1,代表Y被X吃,也是我们指定的意义。
- 所以,这个0,1,2不是随便选的
- Part II - 路径压缩,以及节点间关系确定
- 确定了权值之后,我们要确定有关的操作。
- 我们把所有的动物全初始化。
- struct Animal
- {
- int num; //该节点(node)的编号
- int parent; //该node的父亲
- int relation; //该node与父节点的关系,0同类,1被父节点吃,2吃父节点
- }; Animal ani[50010];
- 初始化为
- For i = 0 to N do
- ani[i].num = i;
- ani[i].parent = i;
- ani[i].relation = 0 ; //自己和自己是同类
- End For
- (1)路径压缩时的节点算法
- 我们设A,B,C动物集合如下:(为了以后便于举例)
- A = { 1 , 2 , 3 ,4 ,5 }
- B = { 6 , 7 , 8 ,9 ,10}
- C = { 11, 12, 13,14,15}
- 假如我们已经有了一个集合,分别有3个元素
- SET1 = {1,2},我们规定集合中第一个元素为并查集的“代表”
- 假如现在有语句:
- 2 2 6
- 这是一句真话
- 2是6的父亲
- ani[6].parent = 2;
- ani[6].relation = 1;
- 那么,6和1的关系如何呢?
- ani[2].parent = 1;
- ani[2].relation = 0;
- 我们可以发现6与2的关系是 1.
- 通过穷举我们可以发现
- ani[now].parent = ani[ani[now].parent].parent;
- ani[now].relation = ( ani[now].relation + ani[now.parent].relation ) % 3;
- 这个路径压缩算法是正确的
- 关于这个路径压缩算法,还有一点需要注意的地方,我们一会再谈
- 注意,根据当前节点的relation和当前节点父节点的relation推出
- 当前节点与其父节点的父节点的relation这个公式十分重要!!
- 它推不出来下面都理解不了!!自己用穷举法推一下:
- 好吧,为了方便伸手党,我给出穷举过程
- i j
- 爷爷 父亲 儿子 儿子与爷爷
- 0 0 (i + j)%3 = 0
- 0 1 (i + j)%3 = 1
- 0 2 (i + j)%3 = 2
- 1 0 (i + j)%3 = 1
- 1 1 (i + j)%3 = 2
- 1 2 (i + j)%3 = 0
- 2 0 (i + j)%3 = 2
- 2 1 (i + j)%3 = 0
- 2 2 (i + j)%3 = 1
- 嗯,这样可以看到,( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation
- 这就是路径压缩的节点算法
- (2) 集合间关系的确定
- 在初始化的时候,我们看到,每个集合都是一个元素,就是他本身。
- 这时候,每个集合都是自洽的(集合中每个元素都不违反题目的规定)
- 注意,我们使用并查集的目的就是尽量的把路径压缩,使之高度尽量矮
- 假设我们已经有一个集合
- set1 = {1,2,7,10}
- set2 = {11,4,8,13},每个编号所属的物种见上文
- set3 = {12,5,4,9}
- 现在有一句话
- 2 13 2
- 这是一句真话,X = 13,Y = 2
- 我们要把这两个集合合并成一个集合。
- 直接
- int a = findParent(ani[X]);
- int b = findParent(ani[Y]);
- ani[b].parent = a;
- 就是把Y所在集合的根节点的父亲设置成X所在集合的根节点。
- 但是,但是!!!!
- Y所在集合的根结点与X所在集合的根节点的关系!!!要怎么确定呢?
- 我们设X,Y集合都是路径压缩过的,高度只有2层
- 我们先给出计算的公式
- ani[b].relation = ( 3 - ani[Y].relation + ( d - 1 ) + ani[X].relation) % 3;
- 这个公式,是分三部分,这么推出来的
- 第一部分,好理解的一部分:
- ( d - 1 ) :这是X和Y之间的relation,X是Y的父节点时,Y的relation就是这个
- 3 - ani[Y].relation = 根据Y与根节点的关系,逆推根节点与Y的关系
- 这部分也是穷举法推出来的,我们举例:
- j
- 子 父相对于子的relation(即假如子是父的父节点,那么父的relation应该是什么,因为父现在是根节点,所以父.relation = 0,我们只能根据父的子节点反推子跟父节点的关系)
- 0 ( 3 - 0 ) % 3 = 0
- 1(父吃子) ( 3 - 1 ) % 3 = 2 //父吃子
- 2(子吃父) ( 3 - 2 ) % 3 = 1 //子吃父,一样的哦亲
- ——————————————————————————————————————————————————————
- 我们的过程是这样的:
- 把ani[Y],先连接到ani[X]上,再把ani[Y]的根节点移动到ani[X]上,最后,把ani[Y]的根节点移动到ani[X]的根节点上,这样算relation的
- 还记得么,如果我们有一个集合,压缩路径的时候父子关系是这么确定的
- ani[爷爷].relation = ( ani[父亲].relation + ani[儿子].relation ) % 3
- 我们已知道,( d - 1 )就是X与Y的relation了
- 而 (3 - ani[Y].relation)就是 以Y为根节点时,他的父亲的relation
- 那么
- 我们假设把Y接到X上,也就说,现在X是Y的父亲,Y原来的根节点现在是Y的儿子
- Y的relation + ani[Y]根节点相对于ani[Y]的relation
- ( ( d - 1 ) + ( 3 - ani[Y].relation) ) % 3
- 就是ani[Y]的父亲节点与ani[X]的relation了!
- 那么,不难得到,ani[Y]的根节点与ani[X]根节点的关系是:
- ( ( d - 1 ) + ( 3 - ani[Y].relation) + ani[X].relation ) % 3 ->应用了同余定理
- 注意,这个当所有集合都是初始化状态的时候也适用哦
- 还是以最开头我们给的三个集合(分别代表三个物种)为例
- 2 1 6
- 带入公式
- ani[6].relation = ( ( 2 - 1 ) + ( 3 - 0 ) + 0 ) % 3 = 1
- 也就是,6被1吃
- Part III - 算法正确性的证明
- 首先,两个自洽的集合,合并以后仍然是自洽的
- 这个不难想吧,数学上有个什么对称性定理跟他很像的。
- 如果理解不了,就这么想!!
- 当set1和set2合并之后,set2的根节点得到了自己关于set1根节点的
- 正确relation值,变成了set1根节点的儿子,那么
- set2的所有儿子只要用
- ( ani[X].relation + ani[Y].relation ) % 3就能得到自己正确的relation值了
- 所以说,针对不在同一集合的两个元素的话,除非违背了(2)和(3),否则永远是真的
- (无论这句话说的是什么,我们都可以根据所给X,Y推出两个子节点之间应有的关系,这个关系一确定,所有儿子的关系都可以确定)
- 其实所有的不同集合到最后都会被合并成一个集合的。
- 我们只要在一个集合中找那些假话就可以了。
- 首先,如何判断
- 1 X Y是不是假话。//此时 d = 1
- if ( X 和 Y 不在同一集合)
- Union(x,y,xroot,yroot,d)
- else
- if x.relation != y.relation ->假话
- 其次,如何判断
- 2 X Y是不是假话 //此时d = 2
- if ( X 和 Y 不在同一集合)
- Union(x,y,xroot,yroot,d)
- else
- (ani[y].relation + 3 - ani[x].relation ) % 3 != 1 ->假话
- 这个公式是这么来的:
- 3 - ani[x].relation得到了根节点关于x的relation
- ani[y] + 3 - ani[x].relation得到了y关于x的relation
- 所以,只要y关于x的relation不是1,就是y不被x吃的话,这句话肯定是假话!
- (2)路径压缩要特别注意的一点(错在这里,要检讨自己)
- 路径压缩的时候,记得要
- 先findParent,再给当前节点的relation赋值。
- 否则有可能因为当前节点的父节点的relation不正确而导致错的稀里哗啦。
- 例子:
- set1 = {1,2,7,10}
- set2 = {3,4,8,11}
- set3 = {12,5,14,9}
- Union(1,3,1,3,1)
- Union(3,12,3,12,2)
- 1 5 1
- 算5的relation
- 如果不先更新parent的relation,算出来应该是
- ( 3 - 0 + 0 + 1 ) % 3 = 1,5被1吃,显然不对
- 这里面,+ 0的那个0是指根节点 12 的relation(未更新,这里的0是指12与11的relation)
- 如果更新完了的话,应该是
- ( 3 - 0 + 2 + 1 ) % 3 = 0 ,5与1是同一物种,对了
- 这里面的 2 是更新节点12的relation(12与1的relation)
- 后记:
- 关于这道题,我在网上搜索了许多解题报告,但是都闪烁其词,大概大家都不想
- 把自己辛辛苦苦推出来的公式写到网上供别人学习来节省时间吧。
- 我觉得这么做不好,对初学者容易产生不良影响,ACM如果只是一个小众化的圈子,那
- 岂不是太没意思了。
- 于是我就把我自己总结的这道题的经验放了出来,希望可以帮得到大家
- 自己总结的,对错也不知道,但是起码是“自洽”的,^ ^
- 感谢那篇博文的博主,也感谢gzm,lqy两位学长的指导。
- c0de4fun
- */
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #include<algorithm>
- using namespace std;
- int father[50005],rank[50005];
- int find(int x)
- {
- if(x==father[x])
- return x;
- int tmp=father[x];
- father[x]=find(tmp);
- rank[x]=(rank[x]+rank[tmp])%3;
- return father[x];
- }
- void liantong(int p,int x,int y)
- {
- int tmp=find(x);
- int tmp1=find(y);
- father[tmp1]=tmp;
- rank[tmp1]=(p+rank[x]+3-rank[y])%3;
- }
- int main()
- {
- int n,k;
- scanf("%d%d",&n,&k);
- //while(>0)
- {
- for(int i=0;i<=n;i++)
- {
- father[i]=i;
- rank[i]=0;
- }
- int ans=0;
- while(k--)
- {
- int tmp,tmp1,tmp2;
- scanf("%d%d%d",&tmp,&tmp1,&tmp2);
- if(tmp==1)
- {
- if(tmp1>n||tmp2>n)
- {
- ans++;
- continue;
- }
- int x=find(tmp1);
- int y=find(tmp2);
- if(x==y)
- {
- int sum=(3-rank[tmp2]+rank[tmp1])%3;
- if(sum!=0)
- ans++;
- }
- else
- {
- liantong(0,tmp1,tmp2);
- }
- }
- else
- {
- if(tmp1>n||tmp2>n||tmp1==tmp2)
- {
- ans++;
- continue;
- }
- int x=find(tmp1);
- int y=find(tmp2);
- if(x==y)
- {
- int sum=(3-rank[tmp2]+rank[tmp1])%3;
- if(sum!=2)
- ans++;
- }
- else
- {
- liantong(1,tmp1,tmp2);
- }
- }
- }
- printf("%d\n",ans);
- }
- return 0;
- }
poj1182(种类并查集好题)的更多相关文章
- 类似区间计数的种类并查集两题--HDU 3038 & POJ 1733
1.POJ 1733 Parity game Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 5744 Accepted: ...
- NOI2001|POJ1182食物链[种类并查集 向量]
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 65430 Accepted: 19283 Description ...
- POJ1182 食物链---(经典种类并查集)
题目链接:http://poj.org/problem?id=1182 食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submission ...
- poj1182 食物链(并查集 好题)
https://vjudge.net/problem/POJ-1182 并查集经典题 对于每只动物创建3个元素,x, x+N, x+2*N(分别表示x属于A类,B类和C类). 把两个元素放在一个组代表 ...
- 【转】并查集&MST题集
转自:http://blog.csdn.net/shahdza/article/details/7779230 [HDU]1213 How Many Tables 基础并查集★1272 小希的迷宫 基 ...
- 【进阶——种类并查集】hdu 1829 A Bug's Life (基础种类并查集)TUD Programming Contest 2005, Darmstadt, Germany
先说说种类并查集吧. 种类并查集是并查集的一种.但是,种类并查集中的数据是分若干类的.具体属于哪一类,有多少类,都要视具体情况而定.当然属于哪一类,要再开一个数组来储存.所以,种类并查集一般有两个数组 ...
- A Bug's Life(种类并查集)(也是可以用dfs做)
http://acm.hdu.edu.cn/showproblem.php?pid=1829 A Bug's Life Time Limit:5000MS Memory Limit:327 ...
- BZOJ 4195: [Noi2015]程序自动分析 [并查集 离散化 | 种类并查集WA]
题意: 给出若干相等和不等关系,判断是否可行 woc NOI考这么傻逼的题飞快打了一个种类并查集交上了然后爆零... 发现相等和不等看错了异或一下再叫woc90分 然后发现md$a \neq b, a ...
- C - BLG POJ - 1417 种类并查集加dp(背包)
思路:刚看这道题感觉什么都不清楚,人物之间的关系一点也看不出来,都不知道怎么写,连并查集都没看出来,但是你可以仔细分析一下,当输入字符串为“yes”的时候,我们设输入的值为x和y,当x为天使是则由题可 ...
随机推荐
- Fusion-io ioDrive Duo Enterprise PCIe Review
原文地址:http://www.storagereview.com/fusionio_iodrive_duo_enterprise_pcie_review As part of StorageRevi ...
- log4j(二)——如何控制日志信息的输出?
一:测试环境与log4j(一)——为什么要使用log4j?一样,这里不再重述 二:先看栗子再来下结论 import org.apache.log4j.*; import test.log4j.bean ...
- 【Algorithm】自顶向下的归并排序
一. 算法描述 自顶向下的归并排序:采用分治法进行自顶向下的程序设计方式,分治法的核心思想就是分解.求解.合并. 先将长度为N的无序序列分割平均分割为两段 然后分别对前半段进行归并排序.后半段进行归并 ...
- 为什么你学不会递归?告别递归,谈谈我的一些经验 关于集合中一些常考的知识点总结 .net辗转java系列(一)视野 彻底理解cookie,session,token
为什么你学不会递归?告别递归,谈谈我的一些经验 可能很多人在大一的时候,就已经接触了递归了,不过,我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给我的感觉就是,递归太神奇了! ...
- Mac OS X各版本号的历史费用和升级关系
Mac OS X各版本号的历史费用和升级关系 OS X 10.6 Snow Leopard 早在2009年10月,Mac OS X10.6雪豹是通过光盘发送.并在英国推出时.费用£25 OS X ...
- Java 内存溢出思维导图
文 by / 林本托 Tips 做一个终身学习的人. 在 Java 内存中,只有一个区域不会发生 OOM 异常,那就是程序计数器内存.下面的思维导图记录了每个内存区域发生内存异常的条件和基本的解决思路 ...
- java对象内存占用
一.前言想知道java对象在内存中的占用情况吗?感谢这位大神的无私分享. http://yueyemaitian.iteye.com/blog/2033046 二.原文的扩充1. 增加了代理jar包的 ...
- 《自己动手写框架2》:用200行的DBF解析器来展示良好架构设计
因为工作关系.须要工作其中,须要读取DBF文件.找了一些DBF读取开源软件,要么是太过庞大,动不动就上万行.要么是功能有问题,编码,长度,总之是没有找到一个很爽的. 在万般无奈之下,我老人家怒从心头起 ...
- 关于easyui combobox下拉框实现多选框的实现
好长时间没有更博了,一是因为最近真的比较忙,二是因为自己是真的偷懒了,哈哈 好啦,这篇博客主要是总结一些关于easyui combobox下拉框实现多选框的实现,包括前台界面的展示,和后台对数据的获取 ...
- java.io.IOException: Incompatible namespaceIDs
问题描述: 在实验的时候,需要往以前的集群中添加一台datanode,在添加之前,由于在调式namenode的时候,格式化了dfs,这就导致了namenode上的namespaceID和以前集群上 ...