并查集真的是一个很有意思的东西,个人感觉它的主要功能就是实现查找和合并,由于用的是树的数据结构,对于这种带权并查集问题,一般都是通过爷爷、父亲、儿子之间的关系,找出合适的数学关系式来进行彼此转换。
题目在这里o_o
题目大意:
中文题目题意很好理解,不过需要注意一点题目中描述的关系:如果A吃B,B吃C,那么C吃A;翻译成树的语言就是父亲吃儿子,儿子吃孙子,而孙子吃爷爷!(并不是我们通常理解的大鱼吃小鱼,小鱼吃虾米……
题目思路:
理解这道题主要是通过向量的思想。
我们为了构造出关系式让父亲和儿子的关系可以用数学式子表现出来,设0表示这个节点和它的父节点时同类、1表示这个节点被它的父节点吃掉、2表示这个节点吃它的父节点;一般的思想是这样,但是我们这样设也并不是没有根据:可以发现,题目中说d=1时表示x、y同类。d=2时表示x吃y,那么d-1就与我们设的x、y的关系一致了(就是说我们可以有一个式子来表示y->x的关系了,即d-1)。同时,这样设也与题目中的要求相符,假设我们已经输入:
2 1 2
2 2 3
此时,1吃2、2吃3,按照题目要求,1与3的关系应该是3吃1;下面考虑我们用数字表达出的关系式,此时r[2]=1,r[3]=1,如果我们再输入:2 3 1,这句话显然是真的,而我们的计算:r[1]=(r[2]+r[3])%3=2,也完全符合!
-------------------这是一个很重要的公式,表明了爷爷与儿子的关系怎么通过父亲和儿子的关系推出,我们先叫它公式1 >o<
1、判断题目中给出的2、3两个条件,即x、y的编号是否大于n以及是不是出现x吃自己的情况,都很好判断,不要忘记就好,之后就是第一个条件。
2、第一个条件的判断的一般思路就是:现在输入了x y,我们可以知道在现在这句话里x、y的关系,这时我们要用这个关系来跟我们根据之前的话里得到的x、y的关系作比较;这样又有两种情况:
----> ①之前得到过x、y的关系,也就是说它们在同一棵树上,即根节点相同 ----<
(下面用y->x来表示y对于x的关系,想一下向量)
根据前面分析,我们知道现在y->x应该是什么关系:d-1;当d==1时,他们是同类,而 r [ x ] 和 r [ y ] 分别代表在前面的话中x、y与根节点的关系,根据等号的传递性,如果 r [ x ] == r [ y ],代表在前面的话里x与y的确是同类;当d==2时,表示x吃y,也就是说y->x 应该为1,我们要判断是不是真话,就要跟前面的话中得到的y->x来比较看他们是否相同,前面的结果是保存在r数组里的,那么怎么用关系数组r描绘出y->x呢?(先脑补向量的一些知识)
我们设x、y的根节点为root,我们现在知道的是x->root(r[x])、y->root(r[y]),如何得到y->x?(请在纸上用向量画出这个关系)根据向量,可以知道 =
+
,那么又有一个问题:
如何求出,问题转化为,怎么根据子节点对父节点的关系来得出 父节点对子节点的关系;由于我们的关系表示在0 1 2三个数中循环,
通过找规律很容易发现:root->x=3-(x->root) ---------------这是另一个很重要的公式^-^设为公式2吧
现在就可以写出,此时y->x=r[y]+3-(r[x])
---->②x、y的关系是新出现的,两者根节点不同----<
现在我们要做的就不是与前面的话进行比较了,而是设法把这次传达过来的x、y的关系保存下来;
首先我们要把他们连在同一棵树上,如果对并查集有一点了解我们就知道,找出x的根节点x1、y的根节点y1之后,可以直接令fa[y1]=x1,这样路径压缩可以减少寻找根节点时花费的时间。但是这时y1的“身份和位置”发生了改变(此时它不再是根节点,需要保存下与根节点x1的关系),我们需要考虑的就是怎样得到y1->x1:
同样考虑向量。可以写出: =
+
+
,我们知道的是r[x]、r[y]和y->x(d-1),
根据公式2可以写出:r[y1]=(3-r[y])+d-1+r[x];
-------------------------------------------我是分割线--------------------------------------------------------------
这就是这道题的思路和推导过程啦,有些细节会在代码中体现。
因为这道题也是困扰我很久总结出来的,可能会有一些表述不清或者错误的地方,希望大家提出啦>o<
下面贴上代码:
#include
#include
#include
#define maxn 50005
using namespace std;
int fa[maxn];
int r[maxn];//r[i]表示i与根节点的关系
//0-与父节点同类,1-被父节点吃,2-吃父节点
int finds(int x)
{
if(x==fa[x])
return x;
else
{
int mid=fa[x];
fa[x]=finds(mid);//先递归这样是相当于一层一层推下来的
r[x]=(r[x]+r[mid])%3;//得出x与自己根节点的关系
return fa[x];
}
}
int main(void)
{
int n,k;
int d,x,y;
int cnt=0;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
fa[i]=i;
r[i]=0;
}
//初始化
while(k--)
{
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n||(d==2&&x==y))
{
cnt++;
continue;
}
int xx=finds(x);
int yy=finds(y);
if(xx!=yy)
{
fa[yy]=xx;//把y的根节点(yy)连接到x的根节点(xx)上后,
r[yy]=(d-1+3-r[y]+r[x])%3;//此时y的根节点(yy)与根节点(xx)的关系
}
else//在同一棵树上
{
if(d==1&&r[x]!=r[y])//假话
cnt++;
if(d==2&&(3-r[x]+r[y])%3!=(d-1))//现在x应该吃y,d-1表示y->x
cnt++;
}
}
printf("%d\n",cnt);
return 0;
}
呼呼