BZOJ-3123: [Sdoi2013]森林(主席樹 + LCA + 啟發式合併)
阿新 • • 發佈:2018-12-19
題目連結:https://www.lydsy.com/JudgeOnline/problem.php?id=3123
題目大意:給出一個有n個節點的森林,接下來有m次操作,每次操作有以下兩種:
1、Q x y k : 詢問節點 x 到節點 y 這條鏈上的點的第 k 大的權值是多少。(x和y保證在一個聯通塊內)
2、L x y : 在節點 x 和節點 y 之間連一條邊。(x和y保證不在一個聯通塊內)
同時這些操作都是強制線上的。
題目思路:如果只有第一種操作,由於是強制線上的,我們優先考慮藉助主席樹和LCA來做,做一遍dfs,每個節點以其父節點為前置節點更新主席樹就行,詢問的時候就是用主席樹查詢
現在來考慮第二種情況,由於x 和 y不屬於同一個聯通塊,那麼這兩個點連一條邊之後,其所在兩個樹會合併成一個新的樹,對於這一個新的樹我們如果重新選擇根節點再重新dfs建主席樹的話,這個時間複雜度是不合理的。
我們可以考慮用啟發式合併的思想對這兩個樹合併,先考慮兩個樹的節點個數,將節點個數較少的點(x)接到節點個數較多的那個樹(y)下方,更新一下節點個數較少的點的根節點為 x 節點,x節點的父節點自然就為y節點了,再對x節點做一遍dfs,更新一下LCA和主席樹的資訊。由於一直是小的往大的去合併,所以總的更新次數就會比你隨便選擇根節點的更新次數要小很多,所以時間複雜度也是合理的。
具體實現看程式碼:
#include <bits/stdc++.h> #define fi first #define se second #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define pb push_back #define MP make_pair #define lowbit(x) x&-x #define clr(a) memset(a,0,sizeof(a)) #define _INF(a) memset(a,0x3f,sizeof(a)) #define FIN freopen("in.txt","r",stdin) #define IOS ios::sync_with_stdio(false) #define fuck(x) cout<<"["<<#x<<" "<<(x)<<"]"<<endl using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int>pii; const int MX = 1e5 + 5; const int N = 4e7; const int inf = 0x3f3f3f3f; int n, m, q; int val[MX]; int sum[N], ls[N], rs[N]; int root[MX], all; struct edge {int v, nxt;} E[MX << 1]; int head[MX], tot; int dep[MX], ST[MX][20], sz[MX]; int vis[MX], belong[MX], tree_rt[MX]; void init(int _n) { for (int i = 1; i <= _n; i++) { vis[i] = belong[i] = root[i] = dep[i] = 0; head[i] = -1; } tot = all = 0; } void add_edge(int u, int v) { E[tot].v = v; E[tot].nxt = head[u]; head[u] = tot++; } void update(int l, int r, int &rt, int pre, int pos, int v) { rt = ++all; ls[rt] = ls[pre]; rs[rt] = rs[pre]; sum[rt] = sum[pre] + v; if (l == r) return; int mid = (l + r) >> 1; if (pos <= mid) update(l, mid, ls[rt], ls[pre], pos, v); else update(mid + 1, r, rs[rt], rs[pre], pos, v); } int query(int l, int r, int u, int v, int lca, int flca, int k) { if (l == r) return l; int res = sum[ls[u]] + sum[ls[v]] - sum[ls[lca]] - sum[ls[flca]]; int mid = (l + r) >> 1; if (res >= k) return query(l, mid, ls[u], ls[v], ls[lca], ls[flca], k); else return query(mid + 1, r, rs[u], rs[v], rs[lca], rs[flca], k - res); } void dfs(int u, int fa, int id) { vis[u] = 1; belong[u] = id; sz[u] = 1; for (int i = 1; i < 20; i++) ST[u][i] = ST[ST[u][i - 1]][i - 1]; update(1, inf, root[u], root[fa], val[u], 1); for (int i = head[u]; ~i; i = E[i].nxt) { int v = E[i].v; if (v == fa) continue; dep[v] = dep[u] + 1; ST[v][0] = u; dfs(v, u, id); sz[v] += sz[u]; } } int LCA(int u, int v) { while (dep[u] != dep[v]) { if (dep[u] < dep[v]) swap(u, v); int d = dep[u] - dep[v]; for (int i = 0; i < 20; i++) if (d >> i & 1) u = ST[u][i]; } if (u == v) return u; for (int i = 19; i >= 0; i--) { if (ST[u][i] != ST[v][i]) { u = ST[u][i]; v = ST[v][i]; } } return ST[u][0]; } void Merage(int u, int v) { int rt1 = tree_rt[belong[u]], rt2 = tree_rt[belong[v]]; if (sz[rt1] > sz[rt2]) swap(rt1, rt2), swap(u, v); add_edge(u, v); add_edge(v, u); ST[u][0] = v; sz[rt2] += sz[rt1]; dep[u] = dep[v] + 1; dfs(u, v, belong[v]); } int main() { // FIN; int T; scanf("%d", &T); scanf("%d%d%d", &n, &m, &q); init(n); for (int i = 1; i <= n; i++) scanf("%d", &val[i]); for (int i = 1; i <= m; i++) { int u, v; scanf("%d%d", &u, &v); add_edge(u, v); add_edge(v, u); } int cnt = 0; for (int i = 1; i <= n; i++) { if (!vis[i]) { dfs(i, 0, ++cnt); tree_rt[cnt] = i; } } char op[2]; int u, v, k; int ans = 0; while (q--) { scanf("%s%d%d", op, &u, &v); u ^= ans; v ^= ans; if (op[0] == 'Q') { scanf("%d", &k); k ^= ans; int lca = LCA(u, v); ans = query(1, inf, root[u], root[v], root[lca], root[ST[lca][0]], k); printf("%d\n", ans); } else Merage(u, v); } return 0; }