poj 1182 食物链 (带关系的并查集)
食物链
Time Limit: 1000MS
Memory Limit: 10000K
Total Submissions: 44835
Accepted: 13069
Description
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
3
Source
总结:用集合维护同时成立的关系
/*
* 对每只动物建立3个元素A,B,C,位置分别为i, i+n, i+2*n
* i-X 表示i属于种类X
* 并查集中每一组表示组内所有元素代表的情况都同时发生或不发生
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int maxn=50000*3+5;
const int maxk=100000+5;
int p[maxn]; void make_set()
{
memset(p, -1, sizeof(p));
} int find_set(int x)
{
return p[x]==-1 ? x : p[x]=find_set(p[x]);
} void union_set(int x, int y)
{
int fx=find_set(x), fy=find_set(y);
if(fx==fy) return;
p[fx]=fy;
} bool same(int x, int y)
{
return find_set(x)==find_set(y);
} int main()
{
int n, k;
scanf("%d%d", &n, &k);
int d, x, y;
int ans=0;
make_set();
for(int i=0;i<k;i++)
{
scanf("%d%d%d", &d, &x, &y);
if(x>n || y>n || (d==2 && x==y)) { ans++; continue; }
x--; y--; if(d==1)//x y 属于同一类
{
if(same(x, y+n) || same(x, y+2*n))
ans++;
else
{
union_set(x, y);
union_set(x+n, y+n);
union_set(x+2*n, y+2*n);
}
}
else if(d==2) {//x 吃 y
if(same(x, y) || same(x, y+2*n))
ans++;
else
{
union_set(x, y+n);
union_set(x+n, y+2*n);
union_set(x+2*n, y);
}
}
}
printf("%d\n", ans); return 0;
}
网上发现另一种解法,但是不是很理解,后面研究一下:
http://endless.logdown.com/posts/2014/04/10/find-sets-notes-differential-statistics-maintenance
http://blog.csdn.net/synapse7/article/details/18517109
http://blog.csdn.net/ditian1027/article/details/20804911
另一种解法:
参考了http://blog.csdn.net/ditian1027/article/details/20804911
http://blog.csdn.net/freezhanacmore/article/details/8767413
我的代码:
/*
* 同一棵树表示这些节点有关系,且关系可推导
* 不同树的相互之间关系不可确定,合并时候需要更新两颗不同树的关系,这里只在union时更新两颗树的父节点关系,find时候再更新本树子节点和父节点的关系
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
const int maxn=50000+5;
int p[maxn];
int r[maxn];//保存与父节点的关系 0 同一类,1被父节点吃,2吃父节点 void make_set()
{
memset(p, -1, sizeof(p));
memset(r, 0, sizeof(r));
} int find_set(int x)
{
if(p[x]==-1) return x; int fx=p[x];
p[x]=find_set(p[x]);
r[x]=(r[x]+r[fx])%3;
return p[x];
} void union_set(int x, int y, int d)
{
int fx=find_set(x), fy=find_set(y);
if(fx==fy) return;
p[fy]=fx;
r[fy]=(3-r[y]+d-1+r[x])%3;//x 吃 y, 所以以 x 的根为父
} bool same(int x, int y)
{
return find_set(x)==find_set(y);
} int main()
{
int n, k;
scanf("%d%d", &n, &k);
int d, x, y;
int ans=0;
make_set();
for(int i=0;i<k;i++)
{
scanf("%d%d%d", &d, &x, &y);
if(x>n || y>n || (d==2 && x==y)) { ans++; continue; }//如果节点编号大于最大编号,或者自己吃自己,说谎 if(same(x, y))//如果原来有关系,也就是在同一棵树中,那么直接判断是否说谎
{
if(d==1 && r[x]!=r[y]) ans++;//如果 x 和 y 不属于同一类
if(d==2 && (r[x]+1)%3!=r[y]) ans++;// 如果 x 没有吃 y
}
else
union_set(x, y, d);
}
printf("%d\n", ans); return 0;
}
总结一下收获:
并查集同一棵树表示他们之间有确定的关系。
原始文章转载一下:
题目就不在这里贴出了。这题目我不会,虽然知道是一道并查集的题目。上网搜答案,乱看一气,有以下几点体会:
- 依然是并查集的find-union框架。
- 除父子关系信息(最基本的并查集)之外,还附加了“与根结点谁吃谁(或者同类)”的信息。
- find函数中,与以往靠while循环寻找始祖不同,这次是递归调用find函数寻找始祖—这导致了路径压缩的根本性改变:沿途的所有结点都直接指向始祖了!
规定
- 若有结点x,那么它的父亲结点是fx。
- 数组r[],它的下标是x,对应此下标储存的数据是x对fx的关系,记为x-->fx。具体而言,数组中储存的元素若是:0 x与fx是同类;1 x被fx吃;2 x吃fx。
至于为什么规定012而不是345,为什么规定0是同类1是被父亲吃2是吃父亲?原则上来讲可以任意规定,只要保证以后的推理都建立在这个规定的基础上即可。
但究竟是为什么?---因为网上都这么规定,那我们也这么规定吧(true story^_^)。事实上,这样的规定确有其方便之处,但只是聊胜于无罢了,这样的规定并非是必须的。
建立基本的关系递推公式
即:已知x-->y、y-->z,求x-->z。
这个表有什么用呢?---更新当前结点的父亲时用。具体而言,若儿子是x,则父亲是fx,爷爷是ffx;现在已知r[x](即x-->fx)和r[fx](即fx-->ffx),若压缩路径使得ffx是x的新父亲,那么新的r[x](即x-->ffx)应该是多少呢?
归纳表中数据,有(x-->ffx)=( (x-->fx) + (fx-->ffx) )%3,即r[x]=(r[x]+r[fx])%3 。这个关系递推表达式很重要,下面的分析就是建立在它的基础上。
递归形式的find函数
[cpp] view plaincopy
- //查找x的集合,回溯时压缩路径,并修改x与father[x]的关系
- int Find_set(int x)
- {
- int t;
- if(x!=father[x])
- {
- t = father[x];
- father[x]= Find_set(father[x]);//递归调用在此
- //更新x与father[x]的关系
- rank[x] = (rank[x]+rank[t])%3;
- }
- return father[x];
- }
举个例子来理解这个过程,如下图:
这个递归形式的find函数有以下两个关键点:
- 回溯时,将沿途所有点的父亲更新成了它的始祖。从上图看出,f4=4、f3=4、f2=4、f1=4都是在回溯过程发生的。
- 由于在1中更新了沿途结点的父亲结点,于是上文的“关系递推公式”在此就派上了用场。
如上图,经过递归函数压缩路径,更新了父结点(1、2、3的父亲现在都是4了),但尚未更新r[1]、r[2]与r[3],那么这个更新过程是什么呢?
3-->4,4-->4,求3-->4。即r[3]=(r[3]+r[4])%3。
2-->3,3-->4(注意:上一句已经把3的父亲更新为4,虽然3的父亲本来就是4),求2-->4。即r[2]=(r[2]+r[3])%3。
1-->2,2-->4(注意:上一句已经把2的父亲更新为4),求1-->4。即r[1]=(r[1]+r[2])%3。
因此在回溯时,也更新了沿途所有结点与他们的新父亲(which is 原来的始祖)的关系。
如何union
有了以上基础,union函数理解起来就是水到渠成的事儿。
什么是union呢?在并查集数据结构中,把本应属于同一个集合,但是目前处在两个不同集合的结点树进行合并的过程就是“并”;那么如何知道两个“本来应该在一起”的结点一开始在不在一起呢?答案自然是“查”。
前文我们已经分析过“查”了,希望大家还记得两点:1.“查”之后,沿途所有结点都有了新的父结点---它们的始祖;2.“查”之后,沿途所有结点与它们共同的新的父结点(which is 它们的始祖)的关系更新完毕。由此可见,在“查”之后,树的高度变为1,一切都变得简单了。所以“查”是整个问题的核心。
题目输入数据的格式是:d x y。d是操作类型,1表示x与y同类,即y-->x的值为0,2表示x吃y,即y-->x的值为1。总结起来就是,y-->x的值恰好是(d-1)。插一句题外话,还记得我曾经提到,本文开头的规定“并非必须”但又“聊胜于无”么?这个规定的方便之处就是你可以用(d-1)这么一个简单的式子描述输入x与输入y的关系,无他。若y-->x的"同类、被父吃、吃父"三种关系被规定成"345"而非"012",那么我们用(d+2)描述y-->x便是。
回归正题,输入d x y,我们先查,查完后树高变为1,如下图:
现在已知:x-->fx、y-->fy以及y-->x,要把fy作为子树与fx树“并”,更新fy的父结点为fx很简单,但怎样计算fy-->fx的值呢?
有了前文关系递推公式的基础,不难看出(fy-->fx)=( (fy-->y) + (y-->x) + (x-->fx) )%3,即r[fy]=( 3-r[y] + d-1 + r[x])%3。
等等,以上操作结束后,r[x]保持不变,r[fy]获得更新,但是本应该更新的r[y]却还是老样子(还是y-->fy,其实应该更新为y-->fx)啊?
事实上,在需要用到y的时候,都会先执行“查”的操作;而这一操作本身会实现将y指向fx,以及更新r[y]。
代码清单
以下代码是这个网页:POJ 1182 食物链【经典并查集应用】中的代码,看了那么多版本,这个最为简洁、清晰,注释也写得很清楚。
[cpp] view plaincopy
- #include<cstdio>
- const int maxn = 50000+10;
- int p[maxn]; //存父节点
- int r[maxn];//存与父节点的关系 0 同一类,1被父节点吃,2吃父节点
- void set(int n) //初始化
- {
- for(int x = 1; x <= n; x++)
- {
- p[x] = x; //开始自己是自己的父亲节点
- r[x] = 0;//开始自己就是自己的父亲,每一个点均独立
- }
- }
- int find(int x) //找父亲节点
- {
- if(x == p[x]) return x;
- int t = p[x];
- p[x] = find(p[x]);
- r[x] = (r[x]+r[t])%3; //回溯由子节点与父节点的关系和父节点与根节点的关系找子节点与根节点的关系
- return p[x];
- }
- void Union(int x, int y, int d)
- {
- int fx = find(x);
- int fy = find(y);
- p[fy] = fx; //合并树 注意:被 x 吃,所以以 x 的根为父
- r[fy] = (r[x]-r[y]+3+(d-1))%3; //对应更新与父节点的关系
- }
- int main()
- {
- int n, m;
- scanf("%d%d", &n, &m);
- set(n);
- int ans = 0;
- int d, x, y;
- while(m--)
- {
- scanf("%d%d%d", &d, &x, &y);
- if(x > n || y > n || (d == 2 && x == y)) ans++; //如果节点编号大于最大编号,或者自己吃自己,说谎
- else if(find(x) == find(y)) //如果原来有关系,也就是在同一棵树中,那么直接判断是否说谎
- {
- if(d == 1 && r[x] != r[y]) ans++; //如果 x 和 y 不属于同一类
- if(d == 2 && (r[x]+1)%3 != r[y]) ans++; // 如果 x 没有吃 y (注意要对应Uinon(x, y)的情况,否则一路WA到死啊!!!)
- }
- else Union(x, y, d); //如果开始没有关系,则建立关系
- }
- printf("%d\n", ans);
- return 0;
- }
后记
- 这个并查集的题目,不仅要更新父结点,同时还要更新与父结点的关系。
- 所以要开辟两个数组,一个用于储存“它的父结点是谁”,另一个用于储存“它与父结点的关系是什么”。经过“查”之后,沿途结点的父结点都变成了它们的始祖结点(在递归回溯时完成),关系当然也要更新为与始祖结点的关系(在递归回溯时,根据关系递推式完成)。
- “并”的过程实际上是对于关系递推式的应用。
poj 1182 食物链 (带关系的并查集)的更多相关文章
- POJ 1182 食物链 (拆点并查集)
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 78601 Accepted: 23422 Description ...
- POJ 1182 食物链 (三态种类并查集)
这题首先不说怎么做,首先要提醒的是..:一定不要做成多组输入,,我WA了一个晚上加上午,,反正我是尝到苦头了,,请诸君千万莫走这条弯路..切记 这题是上一题(Find them and Catch t ...
- [洛谷P2024/POJ1182]食物链 - 带偏移量的并查集(2)
Description 动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形.A 吃 B,B吃 C,C 吃 A. 现有 N 个动物,以 1 - N 编号.每个动物都是 A,B,C 中的 ...
- poj 1182 食物链(关系并查集)
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 62824 Accepted: 18432 Description ...
- poj 1182 食物链 带权并查集
食物链是并查集的进阶运用的一道非常经典的题目. 题目如下: 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A, ...
- POJ 1182 食物链 (带权并查集 && 向量偏移)
题意 : 中文题就不说题意了…… 分析 : 通过普通并查集的整理归类, 能够单纯地知道某些元素是否在同一个集合内.但是题目不仅只有种类之分, 还有种类之间的关系, 即同类以及吃与被吃, 而且重点是题目 ...
- 食物链(带权&种类并查集)
食物链 http://poj.org/problem?id=1182 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 9326 ...
- kuangbin带你飞 并查集 题解
做这套题之前一直以为并查集是很简单的数据结构. 做了才发现自己理解太不深刻.只看重片面的合并集合.. 重要的时发现每个集合的点与这个根的关系,这个关系可以做太多事情了. 题解: POJ 2236 Wi ...
- POJ 1182 食物链 (经典带权并查集)
第三次复习了,最经典的并查集 题意:动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们 ...
随机推荐
- dmesg命令应用
昨晚上线服务的时候,看log偶然发现服务在启动半小时左右就会被supervise重新拉起,也没有core.通过重新启动的服务发现内存飙涨,且持续增加,怀疑是内存打满,进程被kill了. 其实怀疑是正确 ...
- redis命令_ZREVRANGEBYSCORE
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] 返回有序集 key 中, score 值介于 max 和 min 之间(默 ...
- man手册页
man手册页不同页对应的内容: 区段1:用户指令区段2:系统调用区段3:程序库调用区段4:设备区段5:文件格式区段6:游戏区段7:杂项区段8:系统指令区段9:内核内部指令区段n:Tcl或Tk指令
- SpringBoot+Shiro引起事务失效、错误原因、解决方法
一.问题今天发现用户注册的Service的事务并没有起到作用,再抛出一个RuntimeException后,并没有发生回滚,下面是调试步骤: 1.检查数据库的引擎是否是innoDB 2.启动类上是否加 ...
- ORA-01036: 非法的变量名/编号
今天写程序时,往Oracle中插入二进制数据,出现错误ORA-01036:非法的变量名/编号,代码如下: strSql = "INSERT INTO KA99 (KA991,KA992,KA ...
- Uploadify使用源码
上传图片页面绑定源码如下: $("#uploadify").uploadify({ 'uploader' : basePath+'commons/uploadfiles/uploa ...
- Flow construction SGU - 176 有源汇有上下界最小流 二分法和回流法
/** 题目:Flow construction SGU - 176 链接:https://vjudge.net/problem/SGU-176 题意: 有源汇有上下界的最小流. 给定n个点,m个管道 ...
- CentOS安装python setuptools and pip
安装setup-tools wget https://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg --n ...
- java collection 类图
转载:http://visionsky.blog.51cto.com/733317/371809/
- uva748 - Exponentiation 高精度小数的幂运算
uva748 - Exponentiation Exponentiation Problems involving the computation of exact values of very ...