• "科林明伦杯"哈尔滨理工大学第八届程序设计竞赛——Hrbust -2371 GT’s Dream(并查集+树状数组+二分)


    Description
    在现实中认了无数师傅却毫无长进的GT在梦中成为了某武侠世界的神。在这个世界中初始有n个人,他们各成一派。作为世界神GT总共会进行m次操作,每次操作有如下两种情况

    1 x y 表示x所在的帮派吞并了y所在的帮派,若x与y本来就处于同一个帮派则该操作无效。

    2 k 表示GT想要知道当前第k大的帮派有多少人,若当前帮派数量少于k个则输出-1。

    Input

    第一行输入一个T(T<=10)表示有T组测试数据。

    每组数据的第一行包含两个数n,m(n<=100000,m<=300000)表示有n个人,m次操作

    接下来m行包含每一次的操作情况。

    Output
    对于每一个2操作输出当前第k大的帮派有多少人。

    Sample Input
    1

    5 4

    1 2 4

    1 3 4

    2 1

    2 2

    Sample Output
    3

    1

    题意:有n个帮派,每个帮派1人,有m次操作,可以选择1操作让x帮派吞并y帮派,或2操作查询第k大帮派的人数。若K大于实际存在的帮派数量则输出-1

    题解: 首先根据题意帮派的吞并很显然是用并查集实现,加一个数组存储当前帮派的人数即可。
    而查询第k大帮派的人数就要一个前缀和数组来实现,前缀和数组中下标表示人数,每个位置表示该人数的出现次数。也就是有多少个帮派有相同的人数。那么可以发现,此数组的前缀和,即达到某一个人数时的帮派数量总和,下标即是此总和的人数,那么这个总和的下标就是第k小的帮派人数,因为下标是递增顺序增长的,也就是说或要达到这个帮派数k,说明之前有比这个帮派人少的帮派,并且至少有k-1个,那么我们可以求得每个下标的总和,即可以拿前缀和和k比较,若前缀和大于k则说明我们找的下标大了,若小于k说明当前找的的下标并不是第k个小的帮派人数,这样二分查找直到找到一个前缀和等于k的,输出下标即结果。

    至于题目要找的是第k大的,那么我们即可拿总帮派数-k+1得到数组中第k大的应该查找的帮派数量。

    关于几点要注意的,并查集更新帮派时,对某个帮派的人数要初始为0,另一个要加上被吞并的帮派人数。而树状数组中的前缀和则要更新两个帮派原人数的计数,都减去1,并在新人数的计数+1,注意初始化时不仅要初始化并查集数组,还要对每个帮派的人数初始化为1。
    对于真实帮派数real在确定吞并时才修改,有时修改是无效的。

    对于求解k时也是用真实帮派数量。而二分时用的则是N,二分区间是不会改变的,因为总人数不会减少。

    #include<stdio.h>
    #include<string.h>
    int z[100004],tree[100005],num[100005];
    int n,t,m,real;
    int lowbit(int x)///树状数组用于更新不同人数出现的帮派数的前缀和
    {
        return x&(-x);
    }
    int update(int p,int x)
    {
        while(p<=n)
        {
            tree[p]+=x;
            p+=lowbit(p);
        }
    }
    int sum(int p)
    {
        int sum=0;
        while(p>0)
        {
            sum+=tree[p];
            p-=lowbit(p);
        }
        return sum;
    }
    int finds(int x)
    {
        int tmp;
        while(z[x]!=x)
        {
            tmp=x;
            x=z[x];
            z[tmp]=z[x];
        }
        return x;
    }
    void join(int a,int b)
    {
        int fa=finds(a),fb=finds(b);
        if(fa!=fb)
        {
            z[fb]=fa;
            update(num[fa],-1);///当帮派合并,两个帮派的人数都被改变,那么该人数存在的帮派数量也要相应改变
            update(num[fb],-1);
            num[fa]+=num[fb];///表示a吞并b
            num[fb]=0;///b帮派重置为0人
            update(num[fa],1);///新的a帮派人数,将增加一个帮派数量
            real--;///注意真实帮派数的减少,当连接两帮派时不一定减少,因为还有可能是无效操作,确定减少了再减去这个 真实值
        }
    }
    int main()
    {
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d",&n,&m);
            for(int i=1;i<=n;i++) num[i]=1,z[i]=i;
            memset(tree,0,sizeof(tree));
            update(1,n);
            real=n;
            while(m--)
            {
                int flag;
                scanf("%d",&flag);
                if(flag==1)
                {
                    int x,y;
                    scanf("%d%d",&x,&y);
                    join(x,y);
                }
                else
                {
                    int k;
                    scanf("%d",&k);
                    if(k>real)
                    {
                        printf("-1
    ");
                        continue;
                    }
                    k=real-k+1;///树状数组前缀和计算的是第k小人数,那么我要计算的第k大人数,应是实际帮派数-k+1(本身)的位置
                    int l=1,r=n;///要保留原人数的原因,因为二分时做一个最大人数的最大边界,不能因为帮派数量减少就减少搜索区间
                    while(l<r)///根据k值二分查找前缀和中第一个大于等于k的位置,也就是该帮派的人数
                    {
                        int mid=(l+r)/2;
                        if(sum(mid)>=k) r=mid;
                        else l=mid+1;
                    }
                    printf("%d
    ",l);
                }
            }
    
        }
    }
    ///树状数组中存储的是当人数为i时的帮派数量,也是人数的出现次数
    ///如果求第k小人数的帮派人数。那么就是说该人数的前缀和,就包括了所有小于等于该人数的帮派数量,当前缀和大于等于k时,说明该人数之前已经有小于k个帮派,那么数量k的帮派位于的下标即第k小帮派人数
    ///若前缀和,从1到某值的下标,表示不同人数出现的次数,那么前缀和为k表示了在此之前有k-1个帮派存在,那么第k个帮派的人数就是下标
    ///求第k大帮派人数,即当前存在的总帮派数-k值+1得到在树状数组中求第新的k的人数,也就是下标
    
    
  • 相关阅读:
    String painter HDU
    GCD Counting-树形DP
    XOR UVALive
    BZOJ-9-3295: [Cqoi2011]动态逆序对
    E
    Stars HDU
    二维单调队列(理想的正方形+修筑绿化带)
    P3622 [APIO2007]动物园
    COJ1160[一本通 5.3 例 1」Amount of Degrees
    [ZJOI2008]骑士
  • 原文地址:https://www.cnblogs.com/kuronekonano/p/11135812.html
Copyright © 2020-2023  润新知