POJ 2985 The k-th Largest Group 第k大数 Treap / 树状数组 + 并查集

本文介绍使用Treap与树状数组两种数据结构解决一类涉及动态合并及查询的问题。问题背景为给定一定数量的独立集合,通过一系列操作合并某些集合,并查询按大小排序后的第k个集合的规模。文章提供了两种解决方案的详细实现代码,包括Treap的旋转、插入、删除等核心操作,以及树状数组的更新与查找第k大值。

题目链接

题意

n 只猫,m 次操作( n,m2e5 ):
0 i j :将第 i 只猫所在组与第 j 只猫所在组合并;
1 k :询问第 k 大的组中有多少只猫。

法一:Treap

参考资料

董的博客 数据结构之Treap
clj的treap ——wbysr
POJ 2985 Treap平衡树(求第k大的元素) ——潇洒走一回LW

注意点

  1. 无需首先将所有组全部插入。可以在 treap 中只维护 size2 的组。注意一下合并时的 erase insert 即可。

    • 相同的元素无需多次插入。可以在每个节点上记一个 cnt .
    • Code

      #include <cstdio>
      #include <climits>
      #include <cstdlib>
      #include <iostream>
      #define maxn 200010
      using namespace std;
      int fa[maxn], sz[maxn];
      struct node {
          node* ch[2];
          int val, key, sz, cnt;
          node() { sz = 0, cnt = 0, key = INT_MAX; }
          node(int x);
          void update() { sz = ch[0]->sz + ch[1]->sz + cnt; }
      }*null = new node;
      node::node(int x) {
          ch[0] = ch[1] = null;
          val = x, key = rand(), sz = 1, cnt = 1;
      }
      struct treap {
          node* root;
          treap() { root = null; }
          void rotate(node*& t, bool d) {
              node* p = t->ch[d];
              t->ch[d] = p->ch[!d];
              p->ch[!d] = t;
              t->update();
              p->update();
              t = p;
          }
          void insert(node*& t, int x) {
              if (t == null) {
                  t = new node(x);
                  return;
              }
              if (t->val == x) {
                  ++t->cnt;
                  t->update();
                  return;
                  }
              bool dir = x > t->val;
              insert(t->ch[dir], x);
              if (t->ch[dir]->key < t->key) rotate(t, dir);
              else t->update();
          }
          void erase(node*& t, int x) {
              if (t == null) return;
              if (t->val == x) {
                  if (t->cnt > 1) {
                      --t->cnt, t->update();
                      return;
                  }
                  bool d = t->ch[1]->key < t->ch[0]->key;
                  if (t->ch[d] == null) {
                      delete t;
                      t = null;
                      return;
                  }
                  if (t->ch[!d] == null) {
                      node* p = t->ch[d];
                      delete t;
                      t = p;
                      return;
                  }
                  rotate(t, d);
                  erase(t->ch[!d], x);
                  t->update();
                  return;
              }
              bool d = x > t->val;
              erase(t->ch[d], x);
              t->update();
          }
          int calckth(int k) {
              if (k > root->sz) return 1;
              k = root->sz-k+1;
              int dir;
              for (node* t = root; t != null; t = t->ch[dir]) {
                  if (k <= t->ch[0]->sz) dir = 0;
                  else if (k - t->ch[0]->sz - t->cnt <= 0) return t->val;
                  else dir = 1, k -= (t->ch[0]->sz + t->cnt);
              }
          }
          void insert(int x) { insert(root, x); }
          void erase(int x) { erase(root, x); }
          int size() { return root->sz; }
      }* Treap;
      int find(int x) {
          return fa[x] == x ? x : fa[x] = find(fa[x]);
      }
      void unionn(int u, int v) {
          int fau = find(u), fav = find(v);
          if (fau == fav) return;
          if (sz[fau] > 1) Treap->erase(sz[fau]);
          if (sz[fav] > 1) Treap->erase(sz[fav]);
          if (sz[fau] > sz[fav]) swap(fau, fav);
          fa[fau] = fav, sz[fav] += sz[fau];
          Treap->insert(sz[fav]);
      }
      int n, m;
      void work() {
          Treap = new treap;
          for (int i = 1; i <= n; ++i) fa[i] = i, sz[i] = 1;
          while (m--) {
              int x, u, v, k;
              scanf("%d", &x);
              if (x == 0) {
                  scanf("%d%d", &u, &v);
                  unionn(u, v);
              }
              else {
                  scanf("%d", &k);
                  printf("%d\n", Treap->calckth(k));
              }
          }
      }
      int main() {
          while (scanf("%d%d", &n, &m) != EOF) work();
          return 0;
      }
      

      法二:树状数组

      参考资料

      树状数组:第K大值 ——oa414

      思路

      用树状数组维护大小为 size 的组的数量,然后找其中的第 k 大组。
      这里可以用二分,还有一种更为巧妙的方法,具体见上面的链接_(:з」∠)_

      Code

      #include <cstdio>
      #include <climits>
      #include <cstdlib>
      #include <iostream>
      #include <cstring>
      #define maxn 200010
      using namespace std;
      int fa[maxn], sz[maxn], n, m, c[maxn], num;
      int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
      int lowbit(int x) { return x & (-x); }
      void add(int i, int x) { for (; i <= n; i += lowbit(i)) c[i] += x; }
      int sum(int i) { int ret = 0; for (; i; i -= lowbit(i)) ret += c[i]; return ret; }
      void unionn(int u, int v) {
          int fau = find(u), fav = find(v);
          if (fau == fav) return;
          add(sz[fau], -1), add(sz[fav], -1);
          if (sz[fau] > sz[fav]) swap(fau, fav);
          fa[fau] = fav; sz[fav] += sz[fau];
          add(sz[fav], 1);
          --num;
      }
      //int calc(int k) {
      //    int l = 1, r = n;
      //    while (r-l > 1) {
      //        int mid = l + r >>1;
      //        if (sum(mid) >= k) r = mid;
      //        else l = mid + 1;
      //    }
      //    if (sum(l) >= k) return l;
      //    else return r;
      //}
      int calc(int k) {
          int cnt = 0, ans = 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;
      }
      void work() {
          memset(c, 0, sizeof(c));
          for (int i = 1; i <= n; ++i) fa[i] = i, sz[i] = 1;
          add(1, n);
          num = n;
          while (m--) {
              int x, u, v, k;
              scanf("%d", &x);
              if (x == 0) {
                  scanf("%d%d", &u, &v);
                  unionn(u, v);
              }
              else {
                  scanf("%d", &k);
                  printf("%d\n", calc(num-k+1));
              }
          }
      }
      int main() {
          while (scanf("%d%d", &n, &m) != EOF) work();
          return 0;
      }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值