先引用一下这位大佬的题解,对此我再进行细化
题解 P3295 【[SCOI2016]萌萌哒】 - 洛谷专栏https://www.luogu.com.cn/article/it7foeu6并查集
首先是如何想出来需要并查集处理,下面是n=8,第一组约束条件是[1,4]和[5,8],如下图,那么
对应位置就应该是[1->5] [2->6]......这些位置的数字是相同的,就相当于捆绑在一起了,回想并查集就是将具有相同特征的放在一个集合里(具有相同的根),于是我们这里便可以开四个并查集。
逆用st表
引出st表
对上述约束,在添加一条[1,2],[3,4]相等的约束,由于上一条3和7数字相同..那么这个时候3和1相同,那么7和1也应该相同,所以应该对其并查集进行合并,合并到1号这个并查集上,8号合并到2号上,考虑到更改,如果直接改,那就要遍历整段数,对其更改,例如n=32,每次取[1,16] [17,32]....[1,8] [9,16],依次类推,更新的时候就麻烦了,可以自己画个图看一下,时间复杂度n*m,是不可接受的,于是我们考虑是否可以在在某个位置打上标记,等修改结束后,在单独遍历一遍,然后对其更新,类似于线段树懒标记的思想。
然后就是对st表的逆应用
我们在选择标记(merge合并)的时候,首先第一步先把当前能跳的最大的位置个标记上,然后再从当前位置选择继续能跳到最大的位置进行标记。当标记完了之后,我们再对有标记的节点直接的节点打上标记,然后我们选择区间的中点进行,相当与反倍增了。倍增是从两段获取一段,这里是从一段返回去获得两段。如果还不理解可以打个表看看就明白了(就是输出当前f[i][j])。
for(int j=mx;j>=1;j--){for(int i=1;i+(1<<j)-1<=n;i++){int p=find(i,j);merge(i,p,j-1),merge(i+(1<<(j-1)),p+(1<<(j-1)),j-1);}}