题目链接:点击打开链接
题意:有两种操作,合并集合,查询第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;
}