现在的位置: 首页 > 算法 > 正文

poj 2985 The k-th Largest Group 求第K大数 Treap, Binary Index Tree, Segment Tree

2018年12月23日 算法 ⁄ 共 4897字 ⁄ 字号 评论关闭

题目链接:点击打开链接

题意:有两种操作,合并集合,查询第K大集合的元素个数。(总操作次数为2*10^5)


解法:

1、Treap

2、树状数组

     |-二分找第K大数

     |-二进制思想,逼近第K大数

3、线段树

4、。。。


Treap模板(静态数组)

#include <math.h>
#include <time.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
const int maxNode = 500000 + 100;
const int inf = 0x3f3f3f3f;
struct Treap{
    int root, treapCnt, key[maxNode], priority[maxNode],
        childs[maxNode][2], cnt[maxNode], size[maxNode];
    Treap(){
        root = 0; //编号从1开始,0表示为空结点
        treapCnt = 1;
        priority[0] = INT_MAX;
        size[0] = 0;
        //srand(time(0)); //可以不用
    }
    void update(int x){
        size[x] = size[childs[x][0]] + cnt[x] + size[childs[x][1]];
    }
    void rotate(int &x, int t){
        int y = childs[x][t];
        childs[x][t] = childs[y][1-t];
        childs[y][1-t] = x;
        update(x);
        update(y);
        x = y;
    }
    void insert(int &x, int k){
        if(x){
            if(key[x] == k){
                cnt[x]++; //允许有相同结点
            }else{
                int t = key[x] < k;
                insert(childs[x][t], k);
                if(priority[childs[x][t]] < priority[x]){
                    rotate(x, t);
                }
            }
        }else{
            x = treapCnt++;
            key[x] = k;
            cnt[x] = 1;
            priority[x] = rand();
            childs[x][0] = childs[x][1] = 0;
        }
        update(x);
    }
    void erase(int &x, int k){
        if(key[x] == k){
            if(cnt[x] > 1){
                cnt[x]--;
            }else{
                if(childs[x][0]==0 && childs[x][1]==0){
                    x = 0;
                    return ;
                }
                int t = priority[childs[x][0]] > priority[childs[x][1]];
                rotate(x, t);
                erase(x, k);
            }
        }else{
            erase(childs[x][key[x]<k], k);
        }
        update(x);
    }

    int find(int &x, int k){
        if(x){
           if(key[x] == k ) return 1;
           find(childs[k < key[x]], k);
        }
        return 0;
    }

    int kth(int &x, int k){
        if(k <= size[childs[x][0]]){
            return kth(childs[x][0], k);
        }
        k -= size[childs[x][0]] + cnt[x];
        if(k <= 0){
            return key[x];
        }
        return kth(childs[x][1], k);
    }

    int rank(int &x, int k)
    {
        int ret = 0;
        if(key[x] > k) ret = rank(childs[x][0], k);
        else if(key[x] == k) return size[childs[x][0]] + 1;
        else ret = size[childs[x][0]] + 1 +rank(childs[x][1], k) ;
        return ret;
    }
}tp;


int f[maxNode], tot[maxNode];
int findset(int x)
{
    return f[x]==x? x : f[x] = findset(f[x]);
}


int main()
{
    int i, k, x, y, n, m;
    scanf("%d%d",&n,&m);
    for(i=1; i<=n; ++i) {
        f[i] = i;
        tot[i] = 1;
        tp.insert(tp.root, 1);  /*以每一只猫为单位建树*/
    }
    for(i=1; i<=m; ++i)
    {
        scanf("%d",&k);
        if(!k){
            scanf("%d%d",&x, &y);
            x = findset(x);
            y = findset(y);
            if(x == y) continue;
            f[y] = x;
            tp.erase(tp.root, tot[x]);
            tp.erase(tp.root, tot[y]);
            tot[x] += tot[y];
            tot[y] = 0;
            tp.insert(tp.root, tot[x]);
            n--;  //合并两组后,组数减一
        }else{
            scanf("%d", &k);
            printf("%d\n", tp.kth(tp.root, n-k+1));
        }
    }
    return 0;
}

树状数组(二分)

#include <cstdio>

const int maxn = 200000;

int c[maxn + 100],a[maxn + 100], f[maxn + 100];//a[i]表示值为i的数的个数
int lowbit(int x) {return x&-x;}
void update(int i, int val){ for(; i<=maxn; i += lowbit(i)) c[i] += val;}
int getsum(int i){int ret = 0; for(i; i>0; i -= lowbit(i)) ret += c[i]; return ret;}
int findset(int x) {return f[x]==x?f[x]:f[x] = findset(f[x]); }

int main()
{
    int i,n,m,q,x,y,k,l,r;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++) f[i]=i;
    for(i=1;i<=n;i++) a[i]=1;
    update(1,n);//初始状态值为1的数有n个
    int num=n;
    for(i=1;i<=m;i++){
        scanf("%d",&q);
        if(q==0){
            scanf("%d%d",&x,&y);
            x=findset(x);
            y=findset(y);
            if(x==y) continue;
            update(a[x],-1);

            update(a[y],-1);
            update(a[x]+a[y],1);
            f[y]=x;
            a[x]+=a[y];
            num--;//合并集合
        }else{ //二分找第k小的数
            scanf("%d",&k);
            k=num-k+1;//转换为找第k小的数
            int l = 1, r = n;
            while(l <= r)
            {
                int mid = (l + r)>>1;
                if(getsum(mid) >= k) r = mid - 1;
                else l = mid + 1;
            }
            printf("%d\n", l);
        }
    }
    return 0;
}

树状数组(二进制,逼近)

#include <cstdio>

const int maxn = 200000;

int c[maxn + 100],a[maxn + 100], f[maxn + 100];//a[i]表示值为i的数的个数
int lowbit(int x) {return x&-x;}
void update(int i, int val){ for(; i<=maxn; i += lowbit(i)) c[i] += val;}
int findset(int x) {return f[x]==x?f[x]:f[x] = findset(f[x]); }
int find_kth(int k)
{
    int ans=  0, cnt = 0, i;
    for(i=20; i >= 0; --i)///利用二进制的思想,把答案用一个二进制数来表示
    {
        ans += (1<<i);
        if(ans > maxn || cnt + c[ans] >= k)
            ///这里大于等于k的原因是可能会有很多个数都满足cnt + c[ans] >= k,所以找的是最大的满足cnt+c[ans]<k的ans
            ans -= (1<<i);
        else
            cnt += c[ans];///cnt用来累加比当前ans小的总组数
    }///求出的ans是累加和(即小于等于ans的数的个数)小于k的情况下ans的最大值,所以ans+1就是第k大的数
    return ans + 1;
}

int main()
{
    int i,n,m,q,x,y,k,l,r;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++) f[i]=i;
    for(i=1;i<=n;i++) a[i]=1;
    update(1,n);//初始状态值为1的数有n个
    int num=n;
    for(i=1;i<=m;i++){
        scanf("%d",&q);
        if(q==0){
            scanf("%d%d",&x,&y);
            x=findset(x);
            y=findset(y);
            if(x==y) continue;
            update(a[x],-1);

            update(a[y],-1);
            update(a[x]+a[y],1);
            f[y]=x;
            a[x]+=a[y];
            num--;//合并集合
        }else{
            scanf("%d",&k);
            k=num-k+1;//转换为找第k小的数
            printf("%d\n",find_kth(k));
        }
    }
    return 0;
}


线段树

#include <cstdio>
#define LL(x) (x<<1)
#define RR(x) (x<<1|1)
const int maxn = 200005;

int f[maxn], num[maxn]; ///num[i]表示值为i的数的个数
int findset(int x) {return f[x]==x?f[x]:f[x] = findset(f[x]); }
int n, Q;
struct node {
    int l, r;
    int sum;  ///记录:元素个数为i[l<=i<=r]的Group的总个数
}tree[maxn*4];

void build(int rt, int l, int r){
    tree[rt].l = l;
    tree[rt].r = r;
    if(l == 1) tree[rt].sum = n;
    else tree[rt].sum = 0;
    if(l == r) return ;
    int mid = (l + r)>>1;
    build(LL(rt), l, mid);
    build(RR(rt), mid+1, r);
}

void update(int rt, int pos, int val){
    tree[rt].sum += val;
    if(tree[rt].l == tree[rt].r) return;
    int mid = (tree[rt].l + tree[rt].r)>>1;
    if(pos <= mid) update(LL(rt), pos, val);
    else update(RR(rt), pos, val);
}

int query(int rt, int k){ //寻找第k大的数
    if(tree[rt].l == tree[rt].r) return tree[rt].l;
    if(k<=tree[RR(rt)].sum) return query(RR(rt), k);
    else return query(LL(rt), k - tree[RR(rt)].sum);
}
int main(){
    scanf("%d%d",&n,&Q);
    for(int i = 1; i <= n; i ++){
        num[i] = 1; f[i] = i;
    }
    int op,a,b;
    build(1,1,n);
    for(int i = 1;i <= Q; i ++){
        scanf("%d",&op);
        if(op == 0){
            scanf("%d%d",&a,&b);
            int x = findset(a);
            int y = findset(b);
            if(x == y) continue ;
            update(1, num[x], -1);
            update(1, num[y], -1);
            update(1, num[x] + num[y], 1);
            num[x] += num[y];
            f[y] = x;
        }else{
            scanf("%d",&a);
            printf("%d\n",query(1, a));
        }
    }
    return 0;
}

抱歉!评论已关闭.