PKU_1988 并查集的应用

PKU_1988
/*

初看此题像是模拟题,只要模拟30000个方块,并依照它的指示操作,似乎并非很困难。可是,如果有以下的操作(对于n 个方块,t次操作,t>n):

step1        用约n次不到的操作可以将n个方块叠在一个堆上,
step2        用剩余t-n次操作要求堆顶下面含有的方块数

    这样,总共的计算量约有(t-n+1)*n,复杂度约为O(tn),最糟情况有约3e9次计算。对于单case 1000ms来说,很容易超时。

    加速算法的有效手段是用空间换时间,通过预先记录部分方块的值,来避免每次都做重复的计算。首先想到的是记录每一堆的总数,即 a堆有 2 4 5 7;b堆有 1 3 6 ,则记录sum(a)=4 sum(b)=3,那么在通过一次M 4 3(将a堆放在b堆上),c( ai )=i+sum(b) ,c( bi )=i。其中i还是要通过迭代找到元素ai停止来求得。光记录总数是不够的,因为它的最糟情况和上面的最糟情况类似,几乎没有改进。
    因此,可以记录c()来避免多次迭代求i。
    公式改为c( ai )=c( ai )+sum(b) ,为了使堆的表示简化,采用并查集的形式,这样y的代表元素是find_set(y),公式再修改为: (xi表示x的堆中的第i个元素)
        c( xi )=c( xi )+sum(find_set(y))     初始c(i)=0 sum(i)=1


    想法虽好,但实现不好,因为程序需要始终更新c(xi),这个操作同样费事,比如每次对同一个堆上加一块方块,这样更新c(xi)的计算量就是(n-1)*(n-2)/2,是O(n*n),实质上还是平方级的。


    为了替换每次更新的辛苦操作,我们用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初始时做些改动,其实是无碍的)
*/

#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--){
          scanf(" %c",&op);            //    输入操作
          switch(op){
               case 'M'
:               
                   scanf("%d%d",&x,&y)
;
                   union_set(x,y);            //    合并堆操作
                   break;
               case 'C'
:
                   scanf("%d",&x)
;
                   printf("%d ",sum[find_set(x)]-les[x]-1);        //    输出c操作
                   break;
          
}

     }
    return 0;
}
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值