「BJOI2014」大融合
知识点:
原题面 Loj
扯
之前学 LCT 的时候留的坑现在用线段树填上了(
题意简述
给定 \(n\) 个点,\(q\) 次操作。
定义一条边的负载,为它所在的当前能够联通的树上 经过它的简单路径的数量。
操作有两种:
- 给定 \(x,y\),在 \(x,y\) 之间连一条边。保证之前 \(x,y\) 不联通。
- 给定 \(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;
}