<(扩展域/加权)并查集>NOI 2001 食物链
来源:互联网 发布:word2007表格数据求和 编辑:程序博客网 时间:2024/04/27 20:16
可提交的传送门
题目描述 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 Description
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
输出描述 Output Description
只有一个整数,表示假话的数目。
样例输入 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
数据范围及提示 Data Size & Hint
输入文件
对7句话的分析 100 7
1 101 1 假话
2 1 2 真话
2 2 3 真话
2 3 3 假话
1 1 3 假话
2 3 1 真话
1 5 5 真话
Solution 1:加权并查集
- 权值
用rank数组表示:
对于任意一个集合:
1.根节点权值为0
2.吃根节点的权值为2
3.被根节点吃的权值为1
注意:在未路径压缩时,子节点的权值表示的是和它父亲的关系 find操作
对于普通的并查集,我们可以直接一路找到根节点实现路径压缩。但是加权并查集不能这样啊。
对于这样一次查找操作,目前,rank[x]的值是相对于fa[x]的关系。我们要把x合并到fa[fa[x]]上去,就要改变rank[x]的值。
由于三种物种组成的关系呈三角形,我们可以用向量的思维来思考这个问题。
先来直观感受一下,如图,假设rank[x]==1,rank[fa[x]]==2,rank[fa[fa[x]]]==0
rank[x]==1说明fa[x]吃x,而rank[fa[x]]==2说明fa[x]吃fa[fa[x]],所以x和fa[fa[x]]是同类,所以当x指向fa[fa[x]]时,rank[x]=0 。
其实不难发现rank[x]=(rank[x]+rank[fa[x]])%3 ,通过向量,这个关系式显然。merge操作
对于两个并查集的合并,不仅要改变fa数组的值,还要改变rank[fa[x]]
如图,要合并x所在的集合和y所在的集合。
对于已经输入的语句: (设合并后y的权值为rank[y]’)
1.如果x和y是同类即d==1。也就是说合并后,y指向fa[x]时的rank[x]==rank[y]’ ①,也就是图中的蓝色线应等于x指向fa[x]的线。
由向量相加可得:
(rank[y]+rank[fa[y]])%3=rank[y]’②;
联立①②两式可得:(rank[y]+rank[fa[y]])%3=rank[x]
等式变形可得:rank[fa[y]]=(rank[x]-rank[y])%3;
又因为在C++中,模运算有可能得到负数,所以最后的赋值语句就是:rank[fa[y]]=rank(rank[x]-rank[y]+3)%3;
2.如果x吃y即d==2。因为对于吃与被吃的关系,如果x吃y,前面已经得出,合并后y和x的关系应为:rank[y]’=(rank[x]+1)%3①。同样根据向量的知识得:(rank[y]+rank[fa[y]])%3=rank[y]’②
联立①②得:(rank[y]+rank[fa[y]])%3=(rank[x]+1)%3
等式变形得:rank[fa[y]]=(rank[x]-rank[y]+1)%3
同样防止变为负数,再加3:
rank[fa[y]]=(rank[x]-rank[y]+1+3)%3
综上:每次合并只改变合并前集合的父节点的rank值,后面的子节点就在路径压缩中自己更新了对于本题:
1.如果输入的语句表示x > n或y > n或 x吃x 直接ans++
2.如果两个不在一个集合里,肯定是真话,并且合并集合
3.如果两个在同一个集合里,判断两者是否符合读入的关系,如果不符合,ans++
至于如何判断在同一个集合里的两个节点是否符合关系:
很简单:
1)如果d==1,x和y是同类,查看是否rank[x]==rank[y]
2)如果d==2,x吃y,查看是否rank[y]==(rank[x]+1)%3
代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int maxn=100000+50;int n,k,ans;int fa[maxn],rank[maxn];int find(int x){ if(fa[x]==x) return x; int f=fa[x]; fa[x]=find(fa[x]); rank[x]=(rank[x]+rank[f])%3; return fa[x];}bool check(int d,int x,int y){ int f1=find(x),f2=find(y); if(f1==f2) { if(d==1) { if(rank[x]==rank[y]) return true; return false; } else { if(rank[y]==(rank[x]+1)%3) return true; return false; } } else { fa[f2]=f1; if(d==1) rank[f2]=(rank[x]-rank[y]+3)%3; else rank[f2]=(rank[x]-rank[y]+1+3)%3; return true; }}int main(){ int n,k; scanf("%d%d",&n,&k); for(int i=1;i<=n;++i) fa[i]=i; for(int i=1;i<=k;++i) { int d,x,y; scanf("%d%d%d",&d,&x,&y); if(x>n||y>n||(d==2&&x==y)) ans++; else if(!check(d,x,y)) ans++; } printf("%d",ans); return 0;}
其实有一个巧妙的改变,可以节省代码量:
观察:
1.合并操作中:
d==1时,rank[fa[y]]=(rank[x]-rank[y]+3)%3
d==2时,rank[fa[y]]=(rank[x]-rank[y]+1+3)%3
其实直接可以省略为:rank[fa[y]]=(rank[x]-rank[y]+d-1+3)%3
即:rank[fa[y]]=(rank[x]-rank[y]+d+2)%3
2.验证关系是否符合时:
d==1时,rank[x]==rank[y]
d==2时,rank[y]==(rank[x]+1)%3
可以直接省略为:rank[y]==(rank[x]+d-1)%3
所以check函数就可以这样写了:
bool check(int d,int x,int y){ int f1=find(x),f2=find(y); if(f1==f2) { if(rank[y]==(rank[x]+d-1)%3) return true; return false; } else { fa[f2]=f1; rank[f2]=(rank[x]-rank[y]+d+2)%3; return true; }}
Solution 2:扩展域并查集
三倍空间
fa[x] x的同类域
fa[x+n] 吃域
fa[x+2*n] 被吃域
每次合并或查询时直接查询就可以了。
就是有点麻烦,很容易混,多想想就好了
不多说
上代码:
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int maxn=50000+10;int n,k,ans;int fa[maxn*3];int find(int x){ if(fa[x]==x) return x; return fa[x]=find(fa[x]);} bool check(int d,int x,int y){ int x1,x2,x3,y1,y2,y3; x1=find(x);//同类域 x2=find(x+n);//吃域 x3=find(x+(n<<1));//被吃域 y1=find(y); y2=find(y+n); y3=find(y+(n<<1)); if(d==1) { if(x2==y1||x1==y2) return false; fa[x1]=y1; fa[x2]=y2; fa[x3]=y3; return true; } else { if(x3==y1||x1==y1) return false; fa[x1]=y3; fa[x2]=y1; fa[x3]=y2; return true; }}int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n*3;++i) fa[i]=i; for(int i=1;i<=k;++i) { int d,x,y; scanf("%d%d%d",&d,&x,&y); if(x>n||y>n||(d==2&&x==y)) ans++; else if(!check(d,x,y)) ans++; } printf("%d",ans); return 0;}
- <(扩展域/加权)并查集>NOI 2001 食物链
- NOI 2001 食物链 解题报告 (并查集)
- NOI 2001食物链(经典并查集)
- POJ 1182 食物链(加权并查集)
- POJ 1182 食物链 加权并查集
- POJ[1182]食物链 加权并查集
- NOI 2001 食物链 并查集A的第一题。
- 食物链 并查集扩展域
- 【P2024】食物链 (扩展域并查集)
- CODE[VS]1074 食物链 扩展域并查集
- pku 1182 食物链(并查集扩展)
- POJ 1182 (经典食物链 /并查集扩展)
- POJ 1182 食物链 (并查集扩展应用)
- POJ 1182食物链 并查集扩展
- 【并查集】【向量偏移】[NOI 2001]食物链 eat WikiOI 1074
- poj1182 带权并查集 NOI 2001 食物链(eat) P1531
- POJ 1182 / Noi 01 食物链 (并查集&代码优化)
- 并查集(食物链)
- Android 建立文件夹、生成文件并写入文本文件内容
- 【转】Markdown基础语法
- vi中使用xml插件 提示omnifunc未设置错误解决方案
- Easyui linkbutton的启用和禁用
- spark基础之spark sql运行原理和架构
- <(扩展域/加权)并查集>NOI 2001 食物链
- 七-2 管理本地存储,Cookies和资源 检查和删除 Cookie
- yoj更新记录
- Oracle中不同类型的行数据变为列数据的方法
- hanoi双塔问题(高精乘)
- 【机器学习】multi-label 分类
- _strdup、_wcsdup、_mbsdup 浅析
- 连续总结第十三天
- unity 使用百度语音进行语音识别