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;
}