「BJOI2014」大融合

知识点:

原题面 Loj


之前学 LCT 的时候留的坑现在用线段树填上了(


题意简述

给定 \(n\) 个点,\(q\) 次操作。
定义一条边的负载,为它所在的当前能够联通的树上 经过它的简单路径的数量。
操作有两种:

  1. 给定 \(x,y\),在 \(x,y\) 之间连一条边。保证之前 \(x,y\) 不联通。
  2. 给定 \(x,y\),询问 \((x,y)\) 的负载。保证 \((x,y)\) 存在。
    \(1\le n,q\le 10^5\)

分析题意

发现每次加边之后,得到的都是森林。

看到动态森林加边首先想到 LCT 乱搞一波。
用 LCT 维护子树信息,留坑。


操作没有加密,考虑离线把森林建出来,并维护子树大小 \(tree\text{_}size\)
对于连边操作,用并查集简单维护。
对于查询操作,以查询的边为界,将联通树分成两个子树。
答案即 两个子树 \(size\) 之积(这里的 \(size\) 代表联通树子树的 \(size\),不同于森林的 \(tre_size\))。
等价于 联通树 \(size\) 减一个子树的 \(size\)该子树 \(size\) 之积。
联通树 \(size\) 可在连边时通过并查集简单维护,考虑如何得到其中一个子树的 \(size\)

已离线建出了森林,dfs 处理出节点的父子关系 和节点的 dfs 序 \(dfn\)
对于查询的边 \((x,y)\),在联通树中,\(x,y\) 一定是父子关系。
方便起见,查询子节点,即 dfs 序较大的节点的子树 \(size\)

设查询的子树根为 \(u\),则 \(size_u\) 等于 \(u\) 的子树中与 \(u\) 联通的结点数。
又已知 dfs 序,一个节点在 \(u\) 的子树中, 等价于其 dfs 序 在 \([dfn_u, dfn_u+tree\text{_}size_u]\) 中。
\(size_u\) 等于 \(u\)联通树 中,dfs 序在 \([dfn_u, dfn_u+tree\text{_}size_u-1]\) 中的 节点数

发现要维护集合的大小,考虑对每一个联通树维护一个权值线段树。
初始时向第 \(i\) 个联通树的线段树插入 \(dfn_i\)
连边维护并查集时 进行线段树合并即可。


爆零小技巧

注意线段树 Insert 的写法。
如果在分裂区间之前就更新了大小,则不需要 Pushup


代码实现

//知识点:线段树合并,并查集 
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
#define ls (lson[now_])
#define rs (rson[now_])
const int kMaxn = 1e5 + 10;
//=============================================================
int n, m, q[kMaxn][3];
int fa[kMaxn], size[kMaxn];
int edge_num, dfn_num, dfn[kMaxn], tree_size[kMaxn], head[kMaxn], v[kMaxn << 1], ne[kMaxn << 1];
int node_num, root[kMaxn], lson[kMaxn << 5], rson[kMaxn << 5], sum[kMaxn << 5];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void AddEdge(int u_, int v_) {
  v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
int Find(int u_) {
  return u_ == fa[u_] ? u_ : fa[u_] = Find(fa[u_]);
}
void Unite(int u_, int v_) {
  u_ = Find(u_), v_ = Find(v_);
  fa[v_] = u_;
  size[u_] += size[v_];
}
void Dfs(int u_, int fat_) {
  dfn[u_] = ++ dfn_num;
  tree_size[u_] = 1;
  for (int i = head[u_]; i; i = ne[i]) {
    if (v[i] == fat_) continue ;
    Dfs(v[i], u_);
    tree_size[u_] += tree_size[v[i]];
  }
}
void Insert(int &now_, int L_, int R_, int pos_) {
  now_ = ++ node_num;
  sum[now_] ++; //updated
  if (L_ == R_) return ;
  int mid = (L_ + R_) >> 1;
  if (pos_ <= mid) Insert(ls, L_, mid, pos_);
  else Insert(rs, mid + 1, R_, pos_); 
}
int Merge(int x_, int y_, int L_, int R_) {
  if (! x_ || ! y_) return x_ + y_;
  if (L_ == R_) {
    sum[x_] += sum[y_];
    return x_;
  }
  int mid = (L_ + R_) >> 1;
  lson[x_] = Merge(lson[x_], lson[y_], L_, mid);
  rson[x_] = Merge(rson[x_], rson[y_], mid + 1, R_);
  sum[x_] = sum[lson[x_]] + sum[rson[x_]];
  return x_;
}
int Query(int now_, int L_, int R_, int ql_, int qr_) {
  if (! sum[now_]) return 0;
  if (ql_ <= L_ && R_ <= qr_) return sum[now_];
  int mid = (L_ + R_) >> 1, ret = 0;
  if (ql_ <= mid) ret += Query(ls, L_, mid, ql_, qr_);
  if (qr_ > mid) ret += Query(rs, mid + 1, R_, ql_, qr_);
  return ret;
}
//=============================================================
int main() {
  // freopen("merger1.in", "r", stdin);
  // freopen("koishi.out", "w", stdout);
  n = read(), m = read();
  for (int i = 1; i <= m; ++ i) {
    char opt[2]; scanf("%s", opt);
    q[i][0] = (opt[0] == 'Q'), q[i][1] = read(), q[i][2] = read();
    if (! q[i][0]) {
      AddEdge(q[i][1], q[i][2]), 
      AddEdge(q[i][2], q[i][1]);
    }
  }
  for (int i = 1; i <= n; ++ i)  {
    if (! dfn[i]) Dfs(i, 0);
    fa[i] = i;
    size[i] = 1;
    Insert(root[i], 1, n, dfn[i]);
  }
  for (int i = 1; i <= m; ++ i) {
    int opt = q[i][0], u = q[i][1], v = q[i][2];
    if (dfn[u] > dfn[v]) std :: swap(u, v);
    if (opt == 0) {
      u = Find(u), v = Find(v);
      root[u] = Merge(root[u], root[v], 1, n);
      Unite(u, v);
    } else {
      int a = Find(v), sz = size[a];
      int ret = Query(root[a], 1, n, dfn[v], dfn[v] + tree_size[v] - 1);
      printf("%lld\n", 1ll * ret * (sz - ret));
    }
  }
  return 0;
}
posted @ 2020-08-25 09:08  Luckyblock  阅读(290)  评论(0编辑  收藏  举报