为了替换每次更新的辛苦操作,我们用les[ ]数组记录c(xi)上方的元素个数,可以从下面这个方程了解les的作用: c( xi )=sum(find_set(x))-les( xi ) 初始c(i)=0 sum(i)=1
这样,不需要维持一个c数组,只要在执行C操作时,求一下c就可以了。而原本c的维护改为了les的维护。乍看之下,les也不易维护,但结合并查集的路径压缩,不但能灵活更新les数据,并且可以将并查集的路径压缩得很好。
例如对于 a b c d e f g (左边为堆底,右边为堆顶,即c(f)=5 ,以g为该堆代表)
现在将 h i j k 堆在其上方,合并操作如下:
1 les[g]=sum[k] 先更新les[g],a到g元素在查找时边压缩路径边更新
2 sum[k]+=sum[g] 更新sum[k],因为以k为该堆的代表。
而find_set()需要实现如下操作:
当前状态:sum[k]=12 les为在合并操作后的les状态,路径是已压缩
a b c d e f g h i j k
parent g g g g g g k k k k k
les 6 5 4 3 2 1 4 3 2 1 0
例如find_set(a), 则因为递归会调用find_set(f),更新les[ f ]=les[g] +les[f]=5,并压缩f,f 更新完毕;返回到find_set(h),更新les[ h ]=les[g]+les[h]=6,并压缩h。如此反复,便可以得到:
a b c d e f g h i j k
parent k k k k k k k k k k k
les 10 9 8 7 6 5 4 3 2 1 0
虽然用到了递归,但是深度是随着 find_set 的调用而减少直至2层,所以只要经常调用 find_set 路径就可以很快得压缩了,那么递归的消耗根本不算什么。
有了les和sum的计算,所求的c就可以通过 c=sum-les-1 (减一是因为我取堆顶les为0,如果要避免减一则需要在sum的更新和les初始时做些改动,其实是无碍的)
这人理解得好深啊.
Source Code
Problem: 1988 | User: henry11 | |
Memory: 916K | Time: 547MS | |
Language: G++ | Result: Accepted |
- Source Code
#include <stdio.h> #define MAX 30050 // 根据?意,最多30000?方? int p[MAX], sum[MAX],les[MAX]; // p??集合代表,sum和les?助?算 void init() { // 初始化工作 for (int i = 0; i < MAX; i++) { p[i] = i; sum[i]=1; les[i]=0; } } void link(int x, int y) { // ?接??集合 p[y] = x; les[y]=sum[x]; sum[x]+=sum[y]; } int getles(int top,int c){ // ??路?,更新les?? if(p[c]!=top){ les[c]+=getles(top,p[c]); // 更新c的les值 p[c]=top; // ??路? } return les[c]; } int find_set(int d) { // 查找元素在集合的代表 int t=p[d]; if(d!=p[d]){ t=find_set(p[d]); getles(t,d); // 查找的同???路? } return p[d]; // 因???了,所以p[d]才是集合代表 } void union_set(int x, int y) { // 合并??集合 link(find_set(x),find_set(y)); } int main(){ int p,x,y; char op; scanf("%d",&p); // ?入操作?目 init(); while(p--){ getchar(); scanf("%c",&op); // ?入操作 switch(op){ case 'M': scanf("%d%d",&x,&y); union_set(x,y); // 合并堆操作 break; case 'C': scanf("%d",&x); printf("%d/n",sum[find_set(x)]-les[x]-1); // ?出c操作 break; } } return 0; }