PKU1988

为了替换每次更新的辛苦操作,我们用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;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值