【树状数组求第k小+并查集】POJ 2985

http://poj.org/problem?id=2985

树状数组已经够神奇了,原来它还可以求第k小的元素........orz,位运算V5!

#define N (1<<20)
int c[N],rank[N],fa[N];
int n,m;
void init(){
    int i;
    for(i=1;i<N;i++){
        c[i] = 0;
        fa[i] = i;
        rank[i] = 1;
    }
}
int lowbit(int x){
    return x&(-x);
}
int find(int x){
    if(x!=fa[x]){
        fa[x] = find(fa[x]);
    }
    return fa[x];
}
void add(int id,int v){
    while(id<N){
        c[id] += v;
        id += lowbit(id);
    }
}
int sum(int x){
    int ans = 0;
    while(x){
        ans += c[x];
        x -= lowbit(x);
    }
    return ans;
}
//复杂度log(n)
int find_k(int k){
    int ans = 0,cnt = 0;
    for(int i=log(double(N-1))/log(2.0);i>=0;i--){//把树状数组的求和反向模拟,
        ans += (1<<i);
        if(ans>=N || cnt+c[ans]>=k)ans -= (1<<i);
        else cnt += c[ans];
    }
    return ans+1;
}

int main(){
    while(scanf("%d%d",&n,&m) !=-1){
        int i,j;
        init();
        add(1,n);
        while(m--){
            int op;
            scanf("%d",&op);
            if(op==0){
                int a,b;
                scanf("%d%d",&a,&b);
                int ra = find(a);
                int rb = find(b);
                if(ra==rb)continue;
                add(rank[ra],-1);
                add(rank[rb],-1);
                add(rank[ra]+rank[rb],1);
                fa[ra] = rb;
                rank[rb] += rank[ra];
                n--;
            } else {
                int k;
                scanf("%d",&k);
                k = n-k+1;//求第k大=求第n-k+1小
                printf("%d\n",find_k(k));
            }
        }
    }
    return 0;
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

补充:

树状数组实现查找K小的元素

回顾树状数组的定义,注意到有如下两条性质:
一,c[ans]=sum of A[ans-lowbit(ans)+1 ... ans];
二,当ans=2^k时,
 c[ans]=sum of A[1 ... ans];
下面说明findK(k)如何运作:
1,设置边界条件ans,ans'<maxn且cnt<=k;
2,初始化cnt=c[ans],其中ans=2^k且k为满足边界条件的最大整数;
3,找到满足边界条件的最大的ans'使得ans'-lowbit(ans')=ans,即ans'满足c[ans']=A[ans+1 .. ans'](根据性质一),只要将c[ans']累加到cnt中(此时cnt=sum of A[1 ... ans'],根据性质二),cnt便可以作为k的逼近值;
4,重复第3步直到cnt已无法再逼近k,此时ans刚好比解小1,返回ans+1。

其实这个算法就是将树状数组的求和反向........
因此findk(k)的实质就是二分逼近。

#define N (1<<20)
int c[N];
//复杂度log(n)
int find_k(int k){
    int ans = 0,cnt = 0;
    for(int i=20;i>=0;i--){//把树状数组的求和反向模拟,
        ans += (1<<i);
        if(ans>=N || cnt+c[ans]>=k)ans -= (1<<i);
        else cnt += c[ans];
    }
    return ans+1;
}



































  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值