【主席树启发式合并】【P3302】[SDOI2013]森林

Description

给定一个 \(n\) 个节点的森林,有 \(Q\) 次操作,每次要么将森林中某两点联通,保证操作后还是个森林,要么查询两点间权值第 \(k\) 小,保证两点联通。强制在线。

Limitation

\(1~\leq~n,~Q~\leq~80000\)

Solution

考虑有连边还有查询链上第 \(k\) 大,于是要么用 LCT,要么用主席树。

考虑如果用 LCT 的话,并不能快速的维护两点间链的信息(其实感觉在access的时候乱搞一下有希望在多一个 \(\log\) 的代价下维护一颗权值线段树的,但是没有仔细想 ),但是如果使用主席树,在连边的时候可以考虑启发式合并,可以以多一个 \(\log\) 为代价快速合并两个森林。

其实这种合并森林信息的,大概一共就只有 LCT 和启发式合并两种做法吧……

与此类似的在一棵树上合并子树信息的大概只有启发式合并和静态树上链分治两种做法叭……当然不排除有毒瘤题把这个强行转化成子树和父节点连边然后用 LCT 做……启发式合并的例子比如[十二省联考2019]春节十二响,静态树上链分治的例子比如 [CF600E]Lomsat gelral。

于是使用主席树维护每个节点到根的权值线段树即可快速查询链上第 \(k\) 大,在合并森林的时候进行启发式合并。注意到用主席树求链上第 \(k\) 大需要用到两点间 LCA,对于 LCA 的维护可以启发式合并两个森林的倍增数组。合并次数是 \(O(\log n)\) 级别的,每次合并是 \(O(n \log n)\) 的,于是合并总复杂度 \(O(n \log^2 n)\),另外查询复杂度 \(O(q~\log n)\)。所以总的复杂度为 \(O(n~\log^2 n~+~q~\log n)\)。写起来也非常好写,相比于 [HNOI2016]树

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
  const int L = 1000000;
  char buf[L], *front=buf, *end=buf;
  char GetChar() {
    if (front == end) {
      end = buf + fread(front = buf, 1, L, stdin);
      if (front == end) return -1;
    }
    return *(front++);
  }
}

template <typename T>
inline void qr(T &x) {
  char ch = IPT::GetChar(), lst = ' ';
  while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
  while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
  if (lst == '-') x = -x;
}

namespace OPT {
  char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
  if (x < 0) {x = -x, putchar('-');}
  int top=0;
  do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
  while (top) putchar(OPT::buf[top--]);
  if (pt) putchar(aft);
}

const int maxn = 80005;

int lstans, n, m, t;
int ufs[maxn], sz[maxn], MU[maxn], tmp[maxn], anc[18][maxn], depth[maxn];
bool vis[maxn];

struct Tree {
  Tree *ls, *rs;
  int l, r, v;

  ~Tree() {
    if (this->ls) {
      delete this->ls;
      delete this->rs;
    }
  }

  Tree() : ls(NULL), rs(NULL), l(0), r(0), v(0) {}
};
Tree *rot[maxn];

struct Edge {
  int v;
  Edge *nxt;

  Edge(const int _v, Edge *h) : v(_v), nxt(h) {}
};
Edge *hd[maxn];
void cont(const int u, const int v) {
  hd[u] = new Edge(v, hd[u]);
  hd[v] = new Edge(u, hd[v]);
}

void init_hash();
int find(const int x);
int GetLCA(int u, int v);
void dfs(const int u, const int rt);
void build(Tree *u, Tree *pre, const int v);
void buildzero(Tree *u, const int l, const int r);
int query(const Tree *const u, const Tree *const v, const Tree *const x, const Tree *const y, const int k);

int main() {
  freopen("1.in", "r", stdin);
  qr(lstans); lstans = 0;
  qr(n); qr(m); qr(t);
  for (int i = 1; i <= n; ++i) {
    qr(MU[i]);
  }
  init_hash();
  buildzero(rot[0] = new Tree, 1, n);
  for (int i = 1, u, v; i <= m; ++i) {
    u = v = 0; qr(u); qr(v);
    cont(u, v);
  }
  for (int i = 1; i <= n; ++i) if (!vis[i]) {
    dfs(i, i);
  }
  int a, b, c;
  while (t--) {
    char op;
    do op = IPT::GetChar(); while ((op != 'Q') && (op != 'L'));
    if (op == 'Q') {
      a = b = c = 0; qr(a); qr(b); qr(c);
      a ^= lstans; b ^= lstans; c ^= lstans;
      int k = GetLCA(a, b);
      qw(lstans = tmp[query(rot[a], rot[b], rot[k], rot[anc[0][k]], c)], '\n', true);
    } else {
      a = b = 0; qr(a); qr(b);
      a ^= lstans; b ^= lstans;
      int fa = find(a), fb = find(b);
      if (sz[fa] > sz[fb]) {
        std::swap(a, b);
        std::swap(fa, fb);
      }
      sz[fb] += sz[fa];
      anc[0][a] = b;
      dfs(a, fb);
      cont(a, b);
    }
  }
  return 0;
}

void dfs(const int u, const int rt) {
  vis[u] = true;
  sz[u] = 1; ufs[u] = rt;
  depth[u] = depth[anc[0][u]] + 1;
  build(rot[u] = new Tree, rot[anc[0][u]], MU[u]);
  for (int i = 0; i < 17; ++i) {
    anc[i + 1][u] = anc[i][anc[i][u]];
  }
  for (auto e = hd[u]; e; e = e->nxt) if (e->v != anc[0][u]) {
    anc[0][e->v] = u;
    dfs(e->v, rt);
    sz[u] += sz[e->v];
  }
}

void init_hash() {
  memcpy(tmp + 1, MU + 1, n << 2);
  std::sort(tmp + 1, tmp + 1 + n);
  auto ed = std::unique(tmp + 1, tmp + 1 + n);
  for (int i = 1; i <= n; ++i) {
    MU[i] = std::lower_bound(tmp + 1, ed, MU[i]) - tmp;
  }
}

void buildzero(Tree *u, const int l, const int r) {
  if ((u->l = l) == (u->r = r)) return;
  int mid = (l + r) >> 1;
  buildzero(u->ls = new Tree, l, mid); buildzero(u->rs = new Tree, mid + 1, r);
}

void build(Tree *u, Tree *pre, const int v) {
  *u = *pre; ++u->v;
  if (u->l == u->r) return;
  if (u->ls->r >= v) {
    build(u->ls = new Tree, pre->ls, v);
  } else {
    build(u->rs = new Tree, pre->rs, v);
  }
}

int GetLCA(int u, int v) {
  if (depth[u] > depth[v]) {
    std::swap(u, v);
  }
  int delta = depth[v] - depth[u];
  for (int i = 17; delta; --i) if (delta >= (1 << i)) {
    delta -= 1 << i;
    v = anc[i][v];
  }
  if (u == v) return u;
  for (int i = 17; ~i; --i) if (anc[i][u] != anc[i][v]) {
    u = anc[i][u]; v = anc[i][v];
  }
  return anc[0][v];
}

int query(const Tree *const u, const Tree *const v, const Tree *const x, const Tree *const y, const int k) {
  if (u->l == u->r) return u->l;
  int lv = u->ls->v + v->ls->v - x->ls->v - y->ls->v;
  if (lv >= k) {
    return query(u->ls, v->ls, x->ls, y->ls, k);
  } else {
    return query(u->rs, v->rs, x->rs, y->rs, k - lv);
  }
}

inline int find(const int x) {
  return ufs[x] == x ? x : ufs[x];
}

Summary

其实这种合并森林信息的,大概一共就只有 LCT 和启发式合并两种做法吧……

与此类似的在一棵树上合并子树信息的大概只有启发式合并和静态树上链分治两种做法叭……

后面想到会再更新的

原文地址:https://www.cnblogs.com/yifusuyi/p/11108498.html

时间: 2024-11-08 20:24:50

【主席树启发式合并】【P3302】[SDOI2013]森林的相关文章

【主席树 启发式合并】bzoj3123: [Sdoi2013]森林

小细节磕磕碰碰浪费了半个多小时的时间 Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数.第三行包含N个非负整数表示 N个节点上的权值.  接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边, 接下来 T行,每行描述一个操作,格式为“Q x y k”或者“L x y ”,其含义见题目描述部分. Output 对于每

[bzoj3123] [SDOI2013]森林 主席树+启发式合并+LCT

Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数.第三行包含N个非负整数表示 N个节点上的权值. 接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边, 接下来 T行,每行描述一个操作,格式为"Q x y k"或者"L x y ",其含义见题目描述部分. Output 对于每一个第一类

p3302 [SDOI2013]森林(树上主席树+启发式合并)

对着题目yy了一天加上看了一中午题解,终于搞明白了我太弱了 连边就是合并线段树,把小的集合合并到大的上,可以保证规模至少增加一半,复杂度可以是\(O(logn)\) 合并的时候暴力dfs修改倍增数组和维护主席树即可 然后树上主席树就是维护节点到根节点的信息即可, 询问链上的第k大时,画图后可以发现维护一个rootx,rooty,rootlca和rootfalca即可解决问题 注意空间要开够 然后注意细节,没了 #include <cstdio> #include <cstring>

【BZOJ 3123】 [Sdoi2013]森林 主席树启发式合并

我们直接按父子关系建主席树,然后记录倍增方便以后求LCA,同时用并查集维护根节点,而且还要记录根节点对应的size,用来对其启发式合并,然后每当我们合并的时候我们都要暴力拆小的一部分重复以上部分,总时间复杂度为O(n*log),因为每个的节点只会作为小的部分合并,因此他所在的一小部分至少变大2倍,对于每一个节点他作为被合并方最多log次,因此其复杂度为O(n*log),而这个是绝对跑不满还差很多的,我们视他为无常数就好了,当然这一切都是建立在无拆分操作的基础之上,只要有了拆分启发式合并的复杂度就

主席树+启发式合并(LT) BZOJ3123

好久没做题了,写道SBT又RE又T 查询:主席树裸题. 修改:对于两个树合并重建小的树. 注意fa[x][i]重新计算时要清空 #include<cstdio> #include<cctype> #include<cstring> #include<algorithm> using namespace std; inline int read() { char c=getchar();int x=0,sig=1; for(;!isdigit(c);c=get

bzoj3123: [Sdoi2013]森林 主席树+启发式合并

思博题加强版,还是思博题,RT,没了. 内存log^2,写了回收的话可以少个log. lca不能用树剖了好悲伤(IoI),讨厌倍增. 没有1A好不爽啊啊啊,最近写思博题只有一道1A的是要退役的节奏(@[email protected]) #include<cstdio> #include<algorithm> #define N 80005 #define M (l+r>>1) using namespace std; char o[2]; int k,m,q,s,t,

BZOJ3123LCA+主席树+启发式合并

d穆善蓟8悼z旱移7http://www.zcool.com.cn/collection/ZMTg1MjE5MDg=.html 自9B是O焕n偷儆裙1http://www.zcool.com.cn/collection/ZMTg1MjE5NTY=.html 088酥1FVL斗稳7http://www.zcool.com.cn/collection/ZMTg1MjIwNjQ=.html 271U59mYG寂灾http://www.zcool.com.cn/collection/ZMTg1MjIxM

BZOJ 3123 SDOI 2013 森林 可持久化线段树+启发式合并

题目大意:给出一个森林,每个节点都有一个权值.有若干加边操作,问两点之间路径上的第k小权值是多少. 思路:这题和COT1比较像,但是多了连接操作.这样就只能暴力合并连个树.启发式合并会保证时间复杂度不至于太大.然后就是用可持久化线段树维护一个树的信息,按照dfs序来建树,每个节点的可持久化链的参考版本就是它父亲的版本.之后利用权值线段树可区间加减的特性,用f[x] + f[y] - f[lca] - f[father[lca]]来计算权值. CODE: #include <cstdio> #i

P3302 [SDOI2013]森林

传送门 看到森林有合并首先会想到 $LCT$ ,然后发现链上第 $K$ 小不可维护 感觉 $LCT$ 只维护合并也有点大材小用了,考虑合并时直接启发式合并就可以不用 $LCT$ 然后求第 $K$ 小显然考虑主席树 对每个节点维护一个主席树,维护它到树根这的一段区间,那么当前节点的线段树可以直接借用父节点的线段树 并且因为主席树是可加减的,设节点 $x$ 的线段树为 $T[x]$,那么询问是就是在 $T[x]+T[y]-T[lca(x,y)]-T[fa[lca(x,y)]]$ 上面跑 每次启发式合