POJ-1182-食物链
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
题意
- 输入N和K代表N个动物和K个描述,每种描述是同类和吃的关系
- 判断并输出假话总数,详细请看中文题面
题解
- 用sign数组来记录权值,运用带权并查集
- sign数组中,0表示同类,1表示被父结点吃,2表示吃父结点
- 在路径压缩的时候,由于相关人员无论是爸爸还是儿子,都变成了祖先的儿子
- 所以利用多边形的向量关系就可以更新权值
- baba函数代表这两个动物之间有了关系,至于是什么关系,用sign权值表述
- 先判断是否属于第二种或第三种情况,不是的话再调用baba函数判断第一种
- 在用baba函数的时候判断这两个动物是否已有关系,没有的话就更新权值
- 如果已有关系,就判断之前的关系和现在的表述是否矛盾
- 详细请看代码行注释
涉及知识点
- 带权并查集
- 处理 带权并查集 的问题时,关键在于权值的动态变化
- 权值表示谁对谁的关系,就用箭头从谁指向谁
- 形成多边形关系后,常用向量思维进行表达式的推导
- 但要注意如果表达式计算后有负数出现的可能性
- 但原来权值要求非负,勿忘加上形成多边形的结点数
- 如果需要权值在某一范围内,加多了可能出界
- 所以也勿忘形按成多边形的结点数取余喔
- 到底取多少的余和加多少,可根据题意枚举几种情况试一试
- 形成多边形的向量思维示意图
AC代码
#include<stdio.h>
#include<string.h>
const int maxn=50100;
int N,K,D,X,Y,ans;
int father[maxn],sign[maxn];
/*
本题用到带权并查集,所谓的权值就是记录各个结点之间的关系
father数组记录是否与父结点有关系
sign数组记录与父结点的关系是什么
0-同类,1-被父结点吃,2-吃父结点
*/
void init()
{
for(int i=1;i<=N;i++)
{
father[i]=i;//初始化自己都是自己的祖先
sign[i]=0;//初始化自己都是自己的同类
}
}
int find(int x)//完成"查"的功能,找自己的祖先
{
if(x!=father[x])
{
int p=father[x];//过渡变量,记录father[x]的原值,以便做sign变化
father[x]=find(father[x]);
sign[x]=(sign[x]+sign[p])%3;
}
/*
路径压缩的同时,进行权值更新
本来的模板是return x==father[x]?x:father[x]=find(father[x]);
现在为了加上sign的权值变化,分语句清晰
*/
/*
sign[x]是x对爸爸的关系,路径压缩后,x的爸爸是根结点
要表示x与根结点的关系,需要考虑原来x对爸爸和爸爸对根结点的关系
因为原来的爸爸现在也是根结点的儿子,相当于爸爸成了自己的兄弟
所以原来的爷孙三代的关系现在就变成了一个三角形
儿子和爸爸都是爷爷的儿子,假设原来儿子是a,爸爸b,爷爷c
那么如果ab之间有关系,bc之间有关系
现在路径压缩后,ac之间也会有关系,也就是新的sign[x]值
如果a=1,b=2,即a吃b,b被c吃,那么ac同类,新sign[x]=0
其他同理
*/
return father[x];
}
void baba(int x,int y,int D)
{
/*
这题的拜把子并不是说明同一类
而是说明两结点之间已经有了正确的关系
只要是数据里给出过信息了就绑到一起
至于两个结点之间到底是吃、被吃还是同类的关系
靠sign权值来表述
同时,为了有fx和fy的存在,在此函数内需要进行是否是第一种情况的判断
如果不是,则更新结点关系(sign权值)
至于情况2和3应当在主函数调用baba函数之前已经判断完毕
*/
int fx=find(X);
int fy=find(Y);
//找出X和Y的祖先是谁
father[fy]=fx;//连接结点,使得X和Y的find相同,代表他们之间已有关系
if(fx!=fy) sign[fy]=(sign[x]-sign[y]+D+3)%3;
/*
这种情况是没有任何问题的言论
因为在调用此函数之前已经排除了情况2和3的错误
现在只可能是与之前的话冲突
但是还没有关系的两个结点不可能冲突
所以此步骤用来实现权值关联
*/
/*
如果祖先不是同一个,说明还无关,给出关系后,他们需要合并
合并后,Y的祖先,路径压缩后也就是Y的爸爸,他有了爸爸
这个爸爸是X的祖先,也就是路径压缩后X的爸爸
那么这四个人形成了四边形的关系(X,X爸爸,Y,Y爸爸)
他们四个人的关系可以用四边形的向量来解
X指向X的爸爸,Y指向Y的爸爸
Y要与X有关系是题目给出的,题目给了X对Y的关系
所以D--就是Y指向X的sign
画图可得,Y指向X的爸爸的权值可用三角形法则求解
X的爸爸比Y高一级,所以是Y指向X的爸爸
要求的是Y的爸爸与X的爸爸,这俩祖先之间的关系
用最后俩爸爸and一个Y形成的三角形向量求解即可啦
注意可能出现负值,所以要+3,为了不超出012的范围,要%3
*/
else if((sign[y]-sign[x]+3)%3!=D) ans++;
/*
这个时候X和Y的俩祖先相同(路径压缩后同爸爸)
就说明X和Y已经有关系了,再给一次关系就要考虑会不会产生冲突啦
【情况1】当前的话与前面的某些真的话冲突
这个时候用上述的四边形图,求解Y对于X的sign权值
然后判断不等于D就行啦
*/
}
int main()
{
ans=0;
scanf("%d %d",&N,&K);
init();//调用初始化函数
for(int i=1;i<=K;i++)
{
scanf("%d %d %d",&D,&X,&Y);
if(X>N||Y>N) ans++;//【情况2】当前的话中X或Y比N大
else if(D==2&&X==Y) ans++;//【情况3】当前的话表示X吃X
else//可能是真话,还需判断【情况1】
{
D--;
/*
D==1的时候表述同类关系
D==2的时候表述X吃Y的关系
但在sign权值里,D==0表述同类,D==1表述吃,D==2表述被吃
但这是X对Y,在函数里,我们让Y的爸爸认X的爸爸做爸爸
所以D--之后,D的值就表示了Y对于X的关系
*/
baba(X,Y,D);
}
}
printf("%d\n",ans);
return 0;
}
/*
处理带权并查集的问题时,关键在于权值的动态变化
权值表示谁对谁的关系,就用箭头从谁指向谁
形成多边形关系后,常用向量思维进行表达式的推导
但要注意如果表达式计算后有负数出现的可能性
但原来权值要求非负,勿忘加上形成多边形的结点数
如果需要权值在某一范围内,加多了可能出界
所以也勿忘形按成多边形的结点数取余喔
到底取多少的余和加多少,可根据题意枚举几种情况试一试
*/