[好有成就感每个字都自己敲的啊~~被自己感动啦~~]
先谈谈小菜鸟的体会~~:
这道并查集经典权威的题耗了几个小时一字一句看那些最详细的解题报告终于搞明白了。
终于体会到BOBO学长在论坛帖子里说的,刷水题和刷有内涵的题成就感是不同的~嗯...知道这题就可以把对抗赛1的D题解决了。
唔,听闻other's blog的指导,做这题前把1664和2524两道最基础和最简单的并查集先做了,这可以让自己对并查集的原理熟练些。当时做1664的时候还理解了很久,果真是循序渐进的。
不过这题和那两题其实都不一样的,例如在这题中,按秩合并的效果并不明显,故未用。
我也要把自己的解题报告整理出来才好。我希望能把代码写得尽可能简易些...
当然,谢谢这几个博客给出的引导!
题目大意:
A,B,C三种动物,A吃B, B吃C,C吃A。有n个动物,他们编号为1~n。
输入:第一行n,k,分别表示动物个数,给出k句话(有真有假)。接下来n行每行一句话,每句的格式为三个整数:d,x,y。x,y为动物编号,d为1时表示x,y是同类,d为2时表示x吃y。
说明:假话有三种: 1) 当前的话与前面的某些真的话冲突,就是假话; 2) 当前的话中X或Y比N大,就是假话; 3) 当前的话表示X吃X,就是假话。
输出:假话的个数。
思路分析~~:[的确的,建议自己在纸上把思路,推导过程写一写,会容易理解很多]
这是一道带权并查集,且本题只需要用一个并查集
一、relation的确定
对每一个元素,把它对父节点的关系用数组r[i]表示,即relation,作为权值。
由于数字d指定了后面给出的两个动物的关系:1)d=1时,d-1=0,x,y是同类;1)d=2时,d-1=1,x吃y
这个权值不是随便定的噢,且看下面推导~!
因此我们设r[i]包含这三种值,表示动物之间的三种关系:1)r[i]=0时,i与p[i]是同类; 2)r[i]=1时,i被p[i]吃; 3)r[i]=2时,i吃p[i].
注:p[i]为i的父节点。
二、路径压缩 Find_Set(x)的节点算法
我们知道,并查集的集合代表元是一个元素,这里规定集合出现的第一个元素是代表元。
为了使查找效率提高,我们需要使树尽可能低,并查集的路径压缩让它只有一层。
那么怎么把将不是直接联系的两个节点成为父子关系?怎样表明他们之间的关系呢?这是我们接下来要推导的东西~
——> [直接拿别人的例子来~修改了一下]
根据儿子对父亲的关系r和父亲对爷爷的关系r推出儿子对爷爷的关系r:
i j k
爷爷 父亲 儿子 儿子与爷爷
0 0 0=(i + j)%3
0 1 1=(i + j)%3
0 2 2=(i + j)%3
1 0 1=(i + j)%3
1 1 2=(i + j)%3
1 2 0=(i + j)%3
2 0 2=(i + j)%3
2 1 0=(i + j)%3
2 2 1=(i + j)%3
观察即可知道,儿子对爷爷的关系是k=(i+j)%3. (还要这样找规律...没有别人的解题报告我真想不到...)
为啥要这样做?可以画个图理解下。如:X->Y,Y->Z,要把X的父节点设为Z就需要上述步骤。
需要注意的是,每次更新节点的r[i]时应该先Find_Par再更新,否则会因为给出的话是假话而发生错误。
三、集合间关系的确定
初始时候每个元素都是自洽的,那么有几个集合的时候,怎么确定集合(代表节点)之间的关系?因为只有确定两个集合(的代表节点)之间的关系才能把两个集合合并最后得到一个并查集。
先给出公式:r[ry]=(3-r[y]+(d-1)+r[x])%3
我们分3个小部分来推导:[例如,由
Y
->
X->
P
,
Y
->Q,得到Q->
P
.我们需要先得到
Q
->
X
,再得到
Q
->
P
]画图更易理解
(1)——> 由子对父的关系得到父对子的关系
子对父
0(父子同类) ( 3 - 0 ) % 3 = 0
1(父吃子) ( 3 - 1 ) % 3 = 2 //父吃子
2(子吃父) ( 3 - 2 ) % 3 = 1 //子吃父,一样的哦亲
即r[爷爷]=(r[子]+r[父])%3
(2)——>
Q
->
X,
把Y接到X上,使Q成为Y的儿子,进而得到Q对X的relation权值:
r[Q对X]=(
r[Y对X]+r[Q对Y])%3
r[Y对X]=d-1,r[
Q对Y
]=3-r[Y];
故r[Q对X]=((d-1)+(3-r[y]))%3;
(3)——>
Q
->
P,
最后由
Q
->
X->P
得到Q->P(
得到Q对P的relation权值
):
r[Q对X]=(
r[Y对X]+r[Q对Y])%3
r[Q对P]=(
r[Y对X]+r[Q对Y]+r[X对P]
)%3
故r[Q对P]=((d-1)+(3-r[y])+r[x])%3;
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int N=50005;
int p[N],r[N]; //p[i],r[i]分别表示编号为i的动物的父节点,i和父节点的关系
int ans;
void Make_Par(int n) //对每个元素的父节点初始化为自身
{
int i;
for(i=0;i<n;i++)
{
p[i]=i;
r[i]=0;
}
}
int Find_Par(int p[],int r[],int x) //得到元素的父节点,且进行路径压缩:经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,通俗点说就是,每次通过儿子对父亲的关系,父亲对爷爷的关系,得到儿子对爷爷的关系,把儿子指向爷爷,使爷爷成为儿子的父节点
{
int t;
if(x==p[x])
return x;
else
{
t=p[x];
p[x]=Find_Par(p,r,p[x]); //递归寻找x所在集合的代表元素
r[x]=(r[t]+r[x])%3; //更新x与此时代表元的关系
return p[x];
}
}
void Union(int p[],int r[],int x,int y,int rx,int ry,int d)
{
p[ry]=rx;
r[ry]=(r[x]-r[y]+2+d)%3;
}
int main()
{
int n,k,d,x,y,a,b;
scanf("%d%d",&n,&k);
Make_Par(n);
while(~scanf("%d%d%d",&d,&x,&y)&&k--)
{
if(x>n||y>n)
ans++;
else
{
if(d==2&&x==y)
ans++;
else
{
int rx,ry;
rx=Find_Par(p,r,x);
ry=Find_Par(p,r,y);
if(rx!=ry)
Union(p,r,x,y,rx,ry,d);
else
{
if(d==1)
if(r[x]!=r[y])
ans++;
if(d==2)
if(((r[y]+3-r[x])%3)!=1)
ans++;
}
}
}
}
printf("%d\n",ans);
return 0;
}
好像不是很长的说。
后记:
此题有个很坑的地方:只有一组数据,但是有多余数据!(看discuss版~...)所以不能while(~scanf"%d%d",&n,&k),只能也只要读一次。
WA了很多次吧~~祝贺==
争取一类一类把题干掉,如果能以后都不怕并查集也是件好事儿额。