【刷题】LuoguP3224 [HNOI2012]永无乡 线段树合并

题目描述

有一张n个点的无向图,每一个点有一个 [ 1 , n ] [1,n] [1,n]的权值,且互不相等。
支持加入一条边,查询与某个点联通的所有点中权值第 k k k小的点的编号。

思路

数据结构,思想

  • 线段树合并

关于线段树合并

  • 在动态开点线段树上进行
  • 能进行的前提,要么是在所有的线段树中,总的有意义值的数量不多(比如一共就 n n n个,散落在 m m m个线段树里),要么是一些其他特殊性质使得它可以进行。这个算法非常考验我们的分析能力。
  • 很显然,如果我们要合并两棵线段树(假设当前两个点为 n 1 n_1 n1 n 2 n_2 n2,我们要把 n 2 n_2 n2并到 n 1 n_1 n1上面),那么若某一个孩子只有 n 1 n_1 n1或者 n 2 n_2 n2有,那我们大可以直接把 n 1 n_1 n1的那一个儿子连上去,否则就继续递归下去。
  • 可以很容易证明,这一通操作的复杂度是两颗线段树都开了的节点的数量。这是绝对不超过节点个数小的那一个线段树的节点个数的。自然而然地,这可以按秩合并。
  • 复杂度粗略一算是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的,但大部分情况下分析一波,会觉得这是 O ( n l o g n ) O(nlogn) O(nlogn)的。

关于这道题

  • 直接线段树合并即可
  • 我为了方便,还写了一个并查集来维护联通块大小即代表节点(告诉我们线段树根节点编号)

代码

#include<bits/stdc++.h>
#define MAXN 100000
using namespace std;
template<typename T>void Read(T &cn)
{
 char c;int sig = 1;
 while(!isdigit(c = getchar()))if(c == '-')sig = -1; cn = c-48;
 while(isdigit(c = getchar()))cn = cn*10+c-48; cn*=sig;
}
template<typename T>void Write(T cn)
{
 if(cn < 0) {putchar('-'); cn = 0-cn; }
 int wei = 0; T cm = 0; int cx = cn%10; cn/=10;
 while(cn)wei++,cm = cm*10+cn%10,cn/=10;
 while(wei--)putchar(cm%10+48),cm/=10;
 putchar(cx+48);
}
struct qwe{
 struct node{
  int ls,rs;
  int ge;
  void qing() {ls = rs = ge = 0; }
 };
 node t[MAXN*40+1];
 int tlen;
 int ro[MAXN+1];
 void build(int cn)
 {
  tlen = cn;
  for(int i = 1;i<=cn;i++)t[ro[i] = i].qing();
 }
 void jia(int &cn,int cm,int cx,int l,int r)
 {
  if(!cn)t[cn = ++tlen].qing();
  if(l == r){
   t[cn].ge = 1;
   t[cn].ls = cx;
   return;
  }
  int zh = (l+r)>>1;
  if(cm <= zh)jia(t[cn].ls,cm,cx,l,zh); else jia(t[cn].rs,cm,cx,zh+1,r);
  t[cn].ge = t[t[cn].ls].ge + t[t[cn].rs].ge;
 }
 void bing(int cn,int cm,int l,int r)
 {
  if(l == r) {if(!t[cn].ls)t[cn] = t[cm]; return; }
  if(!t[cn].ls)t[cn].ls = t[cm].ls;
  else if(t[cm].ls)bing(t[cn].ls,t[cm].ls,l,(l+r)>>1);
  if(!t[cn].rs)t[cn].rs = t[cm].rs;
  else if(t[cm].rs)bing(t[cn].rs,t[cm].rs,((l+r)>>1)+1,r);
  t[cn].ge = t[t[cn].ls].ge + t[t[cn].rs].ge;
 }
 int cha(int cn,int cm,int l,int r)
 {
//  printf("in cha : cn = %d cm = %d l = %d r = %d t[%d].ge = %d\n",cn,cm,l,r,cn,t[cn].ge);
  if(l == r)return t[cn].ls;
  int zh = (l+r)>>1;
  if(t[t[cn].ls].ge >= cm)return cha(t[cn].ls,cm,l,zh);
  else return cha(t[cn].rs,cm-t[t[cn].ls].ge,zh+1,r);
 }
}T;
int fa[MAXN+1],da[MAXN+1];
int n,m;
int get_fa(int cn) {return fa[cn] == cn ? cn : fa[cn] = get_fa(fa[cn]); }
int main()
{
 Read(n); Read(m);
 for(int i = 1;i<=n;i++)fa[i] = i,da[i] = 1;
 T.build(n);
 for(int i = 1;i<=n;i++) {int bx; Read(bx); T.jia(T.ro[i],bx,i,1,n); }
 for(int i = 1;i<=m;i++)
 {
  int bx,by; Read(bx); Read(by);
  int lin1 = get_fa(bx), lin2 = get_fa(by);
  if(lin1 == lin2)continue;
  if(da[lin1] < da[lin2])swap(lin1,lin2);
  fa[lin2] = lin1;
  da[lin1] += da[lin2];
  T.bing(T.ro[lin1],T.ro[lin2],1,n);
 }
 Read(m);
 for(int i = 1;i<=m;i++)
 {
  char c; int bx,by;
  while(!isalpha(c = getchar()));
  Read(bx); Read(by);
  if(c == 'B'){
   int lin1 = get_fa(bx), lin2 = get_fa(by);
   if(da[lin1] < da[lin2])swap(lin1,lin2);
   if(lin1 == lin2)continue;
   fa[lin2] = lin1;
   da[lin1] += da[lin2];
   T.bing(T.ro[lin1],T.ro[lin2],1,n);
  }
  else{
   int lin = get_fa(bx);
   if(da[lin] < by)Write(-1),puts("");
   else Write(T.cha(T.ro[lin],by,1,n)),puts("");
  }
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值