【题解】P6779 [Ynoi2009] rla1rmdq
奇怪的好题分享。
题意
给定一棵包含 \(n\) 个结点的树,\(m\) 个操作以及一个长度为 \(n\) 的序列 \(a\),树边带权。
令结点 \(x\) 的深度 \(dep(x)\) 为根到 \(x\) 的路径上所有边的权值和,\(fa(x)\) 表示结点 \(x\) 的父亲。对于树根 \(rt\),有 \(fa(rt) = rt\)
对于每次操作,可以:
-
1 l r
,表示 \(\forall l \leq i \leq r\),令 \(a_i \leftarrow fa(a_i)\) -
2 l r
,询问 \(\min_{i = l}^r dep(a_i)\)
\(1 \leq n, m \leq 2 \times 10^5, 1 \leq a_i \leq n\),边权取值范围为 \([0, 10^9]\)
思路
小清新分块。
这种玄学树据撅构第一反应就是奇妙根号算法,但是根号分治一类的东西感觉上没有什么前途,对颜色进行分治理论上也不太可行,于是考虑树上莫队或者分块。
注意到空间限制 64MB,然而时间限制 3s。树上莫队做这东西也不好维护,于是可以合理猜测先用玄学分块草,然后再逐块处理卡空间。根号空间复杂度可过。理论存在,实践开始。
第一眼看上去没有什么好的做法,手推一下发现不太能转化成经典问题,于是我们考虑加上一些特殊条件,看一看是否有启发。
假设全局修改,全局查询。
我们发现:假设有两个结点 \(a, b\),使得 \(b\) 在经过若干次跳跃之后到达了 \(a\) 原本所在的位置,那么 \(a\) 对答案的贡献必然包含 \(b\) 对答案的贡献,于是可以只继续令 \(a\) 向上跳。
换言之,如果某一个结点 \(u\) 向上跳跃,到达已经遍历过的结点,那么这个结点对于答案是无意义的。因此只需要每次将对答案可能有贡献的点暴力向上跳,摊下来复杂度是 \(O(n + q)\)
如果对于根号个整块,都进行一次上面的操作,那么总复杂度是 \(O((n + q) \sqrt{n})\) 的。所以对整块的修改可以直接按上面的方式暴力。
考虑对散块的修改。我们发现对于散块的修改,可能使得它重新成为所处整块中有意义的点。所以我们不妨这样考虑:对于整块修改直接先暴力跳关键点,同时记录下向上跳的步数 \(k\)。但是当修改散块的时候,我们考虑将无意义的点向上跳 \(k\) 步对齐,然后观察它是否重新获得意义。对齐一次的复杂度是 \(O(\sqrt{n} \log n)\) 的,无伤大雅。
找祖先可以考虑跳重链,单次复杂度是 \(O(\log n)\)
于是总复杂度 \(O(n \sqrt{n} \log n)\)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
const int maxm = 2e5 + 5;
const int maxe = 4e5 + 5;
const int blk_sz = 450;
const ll inf = 1e18;
struct node
{
int to, nxt, w;
} edge[maxe];
int n, m, rt, cnt;
int a[maxn];
int que[maxn], up[maxn];
int opt[maxm], ql[maxm], qr[maxm];
int head[maxn], pos[maxn], inv[maxn], top[maxn], fa[maxn], son[maxn], sz[maxn], d[maxn];
ll dep[maxn], ans[maxm];
bool vis[maxn], in_que[maxn];
void add_edge(int u, int v, int w)
{
cnt++;
edge[cnt].to = v;
edge[cnt].nxt = head[u];
edge[cnt].w = w;
head[u] = cnt;
}
void dfs1(int u, int f)
{
fa[u] = f, sz[u] = 1;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if (v != f)
{
dep[v] = dep[u] + edge[i].w;
d[v] = d[u] + 1;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
}
void dfs2(int u, int t)
{
pos[u] = ++cnt;
inv[cnt] = u;
top[u] = t;
if (son[u]) dfs2(son[u], t);
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].to;
if ((v != fa[u]) && (v != son[u])) dfs2(v, v);
}
}
int kth_anc(int u, int k)
{
if (k >= d[u]) return rt;
while (k > d[u] - d[top[u]])
{
k -= (d[u] - d[top[u]] + 1);
u = fa[top[u]];
}
return inv[pos[u] - k];
}
void solve(int st, int ed)
{
int blk_cnt = 0, qlen = 0, to;
ll res = inf;
memset(up, 0, (n + 1) * sizeof(int));
memset(que, 0, (n + 1) * sizeof(int));
memset(vis, false, (n + 1) * sizeof(bool));
memset(in_que, false, (n + 1) * sizeof(bool));
for (int i = st; i <= ed; i++)
{
if (!vis[a[i]])
{
vis[a[i]] = in_que[i] = true;
que[++qlen] = i;
res = min(res, dep[a[i]]);
}
}
for (int i = 1; i <= m; i++)
{
int l = max(ql[i], st), r = min(qr[i], ed);
if (l > r) continue;
if (opt[i] == 1)
{
if ((l == st) && (r == ed))
{
int tlen = qlen;
blk_cnt++, qlen = 0;
for (int j = 1; j <= tlen; j++)
{
up[que[j]]++;
a[que[j]] = fa[a[que[j]]];
if (!vis[a[que[j]]])
{
vis[a[que[j]]] = true;
res = min(res, dep[a[que[j]]]);
que[++qlen] = que[j];
}
else in_que[que[j]] = false;
}
}
else
{
for (int j = l; j <= r; j++)
{
to = kth_anc(a[j], blk_cnt - up[j] + 1);
a[j] = to, up[j] = blk_cnt;
if (!vis[to])
{
vis[to] = true;
res = min(res, dep[to]);
if (!in_que[j]) in_que[j] = true, que[++qlen] = j;
}
}
}
}
else
{
if ((l == st) && (r == ed)) ans[i] = min(ans[i], res);
else
{
for (int j = l; j <= r; j++)
{
to = kth_anc(a[j], blk_cnt - up[j]);
ans[i] = min(ans[i], dep[to]);
a[j] = to, up[j] = blk_cnt;
}
}
}
}
}
int main()
{
int st, ed;
int u, v, w;
scanf("%d%d%d", &n, &m, &rt);
for (int i = 1; i <= n - 1; i++)
{
scanf("%d%d%d", &u, &v, &w);
add_edge(u, v, w);
add_edge(v, u, w);
}
cnt = 0;
dfs1(rt, 0);
dfs2(rt, rt);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= m; i++) scanf("%d%d%d", &opt[i], &ql[i], &qr[i]), ans[i] = inf;
int len = (n + blk_sz - 1) / blk_sz;
for (int i = 1; i <= len; i++)
{
st = (i - 1) * blk_sz + 1, ed = (i == len ? n : i * blk_sz);
solve(st, ed);
}
for (int i = 1; i <= m; i++)
if (opt[i] == 2) printf("%lld\n", ans[i]);
return 0;
}