ACM练级日志:带权并查集与食物链

时间:2022-06-06 19:47:52

最近终于干掉了高中两年都没有搞定的题目:食物链,就是那个A吃B,B吃C,C吃A这道NOI的经典题。当年自己写了200多行,把自己都写碎了,也没弄出来,最近学习了带权并查集,终于搞定了这道题。


首先说说并查集为什么能带权值……  原先,最基本的并查集是维护等价关系了,如果A跟B处于同一集合,那说明A和B就是等价的。但是如果给并查集每个边覆上权值的话,那么并查集就不光能表示等价关系,而且只要是“有关系”,就能表示。


当时我见到了这么一道题:我有变量A到Z,每次我会告诉你两个变量的差(A-C=3 , R-S=2),然后途中我也会提问两个变量的差是多少,你要回答我“是多少”,或者“不确定”。这里我们就可以用带权并查集:如果两个变量之间已经“有关系”,那么就合并入一个集合,每个元素和它父亲的边权值就是这个差值。例如我们会有A->C =3, R->S=2,如果又有C->R=1,则沿着这条路走下去,就有了A->S=6。我们成功地维护了一个表示“有关系”的并查集,同时我们还用边的权值维护了这个关系是什么。


不妨再看看NOIP的关押罪犯,简化来讲,关键一步就是每次给你两个数A,B,告诉你A,B必须不能在一个*里,问你是否可行。那么,对于这样的关系,我们可以有A->B=1表示A和B不在一起,0表示在一起,然后边的权值都要模2.这样,如果A->B=1, B->C=1,则A->C=0,这个时候如果我告诉你A->C=1,就肯定没戏了。


食物链的解题报告有很多,我觉得最好的解法就是带权并查集,上面那个关押罪犯搞明白了,这种问题就都不是问题了。唯一值得注意的是,我在很多地方看到在维护权值的时候,作者用的都是找规律的办法,其实用向量的思想去想会非常简明而且绝不会出错。举个例子,最难的一步“合并”,假设我们合并x1, x2, x1的祖宗叫px1, x2的祖宗叫px2,我们在进行find(x1),find(x2)操作以后,就知道了x1->px1 = value[x1],x2->px2 = value[x2], 同时我们想添加的关系就是x1->x2 = d,现在关键问题是我们要知道px1 -> px2应该是多少(假设把x1并到x2去),那么根据向量的思想,很容易我们就得出,px1 -> px2 = px1->x1 + x1->x2 + x2->px2 = -value[x1] + d + value[x2] ,需要的话再取模即可。


(注意!! 在对m取模之前,一定要加上m,不然如果d=value[x2]=0,你就死定了,1小时人生换来的教训,食物链标程里面有,但是当时我没注意,付出代价了……  一定一定写成 (-value[x1] + d + value[x2] + m)%m 。 )


同样的,find的时候,我们假设要find(x1),x1当前的父亲是px1, 我们用一个p_old来代表px1,假设新的父亲是p_new,我们想知道x1->p_new是多少。已知的是x1->p_old, 而p_old->p_new 在递归的时候已经更新了,所以我们发现把这两个向量加起来就是x1->p_new,就可以很欢快地维护权值了。