【SD集训】20230417 T2 b(b) 【大联盟】20230531 T3 树上查询(tree)
b
两个密码不知道设哪个?那就不设了吧,还据说是原题。
题目描述
给定 \(n\) 个顶点的树,顶点编号为 \(1,2,\cdots,n\),给定长度 \(n_0\) 的序列 \(a_1,a_2,\cdots, a_{n_0}\),共 \(m\) 次查询,每次查询给定 \(l, r, x\),问树的顶点 \(x\),依次向 \(a_l,a_{l+1},\cdots,a_r\) 移动一步,到达的顶点。
若 \(x=y\),则从顶点 \(x\) 向 \(y\) 移动一步到达 \(y\),否则到达与 \(y\) 在树上相邻且距离 \(y\) 最近的位置。
数据范围:\(1\le n,n_0,m\le 10^5\)。
题解
很 lxl 的题。
解法 \(1\):官方做法
首先对操作序列分块,块长为 \(B\)。考虑预处理出 \(f_{i,j}\) 表示经过第 \(i\) 个块内的操作,本来位于 \(j\) 的点,之后在哪里。
如何求呢?首先,由于一个块内操作序列的点只有 \(B\) 个,所以对这 \(B\) 个点建出虚树。不难发现,每次操作同一条虚边上的点移动方向相同,而且是整天向上、向下移动,于是考虑用 deque 维护,@帅气yuyue(雾。
考虑这若干点可能移动到同一个点上,于是需要快速支持合并,可以用链表来维护,即 deque <list <int>>。
当然,并不是所有点都初始在虚树上,所以,以所以虚树上的点为源点跑 bfs,算出每个点距离最近的虚树上的点的距离,以及对应的点。
所以,预处理的复杂度就能做到 \(O(\frac{n n_0}{B}+nB)\)
考虑查询,首先散块的暴力查询可以使用长链剖分 \(O(1)\) 求树上 \(k\) 级祖先来求;而整块用预处理的值。
所以,查询的复杂度就能做到 \(O(m(B+\frac{n_0}{B}))\)。
视 \(n,n_0,m\) 同阶,时间复杂度为 \(O(n\sqrt{n})\)。
注意:list merge 的复杂度是 \(O(|A|+|B|)\),splice 的复杂度是 \(O(1)\) 但是常数巨大。所以 list 需要手写来卡常。
至于 deque 倒没有必要手写就能过了。
代码
记录。
#include <bits/stdc++.h>
#define SZ(x) (int) x.size() - 1
#define all(x) x.begin(), x.end()
#define ms(x, y) memset(x, y, sizeof x)
#define F(i, x, y) for (int i = (x); i <= (y); i++)
#define DF(i, x, y) for (int i = (x); i >= (y); i--)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
const int N = 1e5 + 10, B = 320;
int n, n0, m, a[N], dfn[N], rdfn[N], t[N], tot, fa[21][N], mx[N], son[N], dd[N], st[21][N], lg[N], dep[N], f[N], num[N], g[N], ff[N], pre[N / B + 5][N], id[N];
vector <int> v[N], vv[N], line[N];
int qq(int x, int y) {
return dep[x] < dep[y] ? x : y;
}
void dfs(int x) {
mx[x] = dep[x] = dep[fa[0][x]] + 1;
for (int i: v[x]) {
dfs(i);
if (mx[i] > mx[son[x]]) {
son[x] = i;
mx[x] = mx[i];
}
}
}
void dfs3(int x) {
dd[dfn[x] = ++tot] = x;
st[0][tot] = fa[0][x];
if (son[x]) dfs3(son[x]);
for (int i: v[x])
if (i != son[x]) dfs3(i);
rdfn[x] = tot;
}
int lca(int x, int y) {
if (x == y) return x;
x = dfn[x]; y = dfn[y];
if (x > y) swap(x, y);
int k = lg[y - x++];
return qq(st[k][x], st[k][y - (1 << k) + 1]);
}
queue <int> q;
int listval[N], nxt[N], listtot;
struct List {
int l, r;
List(int L = 0, int R = 0) {
l = L; r = R;
}
void merge(List x) {
if (!x.l) return;
if (!l) {
l = x.l;
r = x.r;
} else {
nxt[r] = x.l;
r = x.r;
}
}
};
List nwlist(int x) {
listtot++; nxt[listtot] = 0; listval[listtot] = x;
return {listtot, listtot};
}
deque <List> s[B * 2 + 5];
List emp, tanp[B * 2 + 5];
vector <pair <int, int>> bru[B + 5];
int vis[N];
void dfs2(int x) {
for (int i: vv[x]) {
dfs2(i);
s[id[i]].resize(dep[i] - dep[x]);
line[i].clear();
int k = i;
while (k != x) {
g[k] = i, f[k] = 0, num[k] = k, q.push(k);
line[i].push_back(k);
k = fa[0][k];
} reverse(all(line[i]));
}
}
int ask(int x, int k) {
if (!k) return x;
int t = lg[k];
x = fa[t][x]; k -= (1 << t);
return fa[t][dd[dfn[x] + (1 << t) - k]];
}
int jump(int x, int y) {
if (x == y) return x;
if (dfn[x] <= dfn[y] && dfn[y] <= rdfn[x]) return ask(y, dep[y] - dep[x] - 1);
return fa[0][x];
}
signed main() {
read(n); read(n0); read(m);
F(i, 2, n) lg[i] = lg[i >> 1] + 1;
F(i, 2, n) {
read(fa[0][i]);
v[fa[0][i]].push_back(i);
} dfs(1);
dfs3(1);
F(i, 1, 20)
F(j, 1, n)
fa[i][j] = fa[i - 1][fa[i - 1][j]];
F(i, 1, lg[n])
for (int j = 1; j + (1 << i) - 1 <= n; j++)
st[i][j] = qq(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]);
F(i, 1, n0) read(a[i]);
line[1].push_back(1);
g[1] = num[1] = 1;
for (int l = 1, r, id = 1; l <= n0; l = r + 1, id++) {
listtot = 0;
r = min(l + B - 1, n0);
int len = r - l + 1;
F(i, l, r) t[i - l + 1] = a[i];
t[++len] = 1;
sort(t + 1, t + len + 1, [&] (int x, int y) {
return dfn[x] < dfn[y];
});
int ttmp = len;
F(i, 1, ttmp - 1) t[++len] = lca(t[i], t[i + 1]);
sort(t + 1, t + len + 1, [&] (int x, int y) {
return dfn[x] < dfn[y];
});
len = unique(t + 1, t + len + 1) - t - 1;
F(i, 1, len) s[i].clear(), vv[t[i]].clear(), ::id[t[i]] = i;
F(i, 1, len - 1) {
int k = lca(t[i], t[i + 1]);
vv[k].push_back(t[i + 1]);
ff[t[i + 1]] = k;
}
s[1].resize(1);
q.push(1);
ms(f, 0x3f);
f[1] = 0;
dfs2(1);
while (q.size()) {
int x = q.front(); q.pop();
for (int i: v[x])
if (f[x] + 1 < f[i]) {
f[i] = f[x] + 1;
num[i] = num[x];
q.push(i);
}
}
F(i, 1, n)
if (f[i] <= r - l) bru[f[i]].emplace_back(num[i], i);
else pre[id][i] = ask(i, r - l + 1);
F(i, l, r) {
for (auto [j, k]: bru[i - l]) s[::id[g[j]]][dep[j] - dep[ff[g[j]]] - 1].merge(nwlist(k));
bru[i - l].clear();
int tt = a[i];
while (tt) {
vis[ff[tt]] = tt;
tt = ff[tt];
}
F(j, 1, len)
if (vis[t[j]] || t[j] == a[i]) {
tanp[j] = s[::id[t[j]]].back();
s[::id[t[j]]].pop_back();
s[::id[t[j]]].push_front(emp);
} else {
tanp[j] = s[::id[t[j]]].front();
s[::id[t[j]]].pop_front();
s[::id[t[j]]].push_back(emp);
}
F(j, 1, len)
if (vis[t[j]]) s[::id[vis[t[j]]]].front().merge(tanp[j]), vis[t[j]] = 0;
else {
if (t[j] == a[i]) s[::id[a[i]]].back().merge(tanp[j]);
else s[::id[ff[t[j]]]].back().merge(tanp[j]);
}
}
F(i, 1, len)
F(j, 0, dep[t[i]] - dep[ff[t[i]]] - 1) {
int kk = s[::id[t[i]]][j].l;
while (kk) {
pre[id][listval[kk]] = line[t[i]][j];
kk = nxt[kk];
}
}
}
while (m--) {
int l, r, x; read(l); read(r); read(x);
int idl = (l - 1) / B + 1, idr = (r - 1) / B + 1;
if (idl == idr) {
F(i, l, r) x = jump(x, a[i]);
} else {
F(i, l, idl * B) x = jump(x, a[i]);
F(i, idl + 1, idr - 1) x = pre[i][x];
F(i, (idr - 1) * B + 1, r) x = jump(x, a[i]);
} cout << x << '\n';
}
return 0;
}
解法 \(2\):1kri 的做法
mwr /bx
先考虑一条链的做法,我们记录一个询问现在到了哪个点,在 \(l\) 处插入询问,\(r\) 后单点查询。
每次是将所有编号 \(<a\) 的询问点编号 \(+1\),\(>a\) 的 \(-1\)。
可以平衡树分裂后打加法标记。
拓展到树,考虑树剖。我们是将a祖先的询问点向下拉,其他点向上拉。
先考虑向上拉的过程,除了所有树链链顶节点,其他的都是将 \(dfn\) 序 \(-1\)。我们对于这些点暴力。
再考虑向下拉的过程,除了 \(a\) 向上的 \(\log\) 条树链链顶的父亲,其他都是将 \(dfn\) 序 \(+1\)。我们对于这些点暴力。
我们分析下复杂度:除了暴力的部分,每次是划分成 \(\log\) 段 \(dfn\) 区间进行修改,而暴力的复杂度可以均摊,我们设势能函数为所有点上方轻边数之和。一次暴力上拉使势能 \(-1\),一次下拉使势能 \(-1\),但对于每个 \(a\) 只会下拉 \(\log\) 次。
下拉时要注意将一个点上挂的所有询问并查集合并,只留一个询问,要么势能变化量不是 \(1\)。
总势能 \(O(n\log n)\),每次都是 \(O(\log n)\) 的平衡树操作,所以总时间复杂度 \(O(n\log ^ 2 n)\)。
代码
太难写了,先咕了。

浙公网安备 33010602011771号