Luogu P4211 [LNOI2014]LCA 题解
Description
给出一个 nn 个节点的有根树(编号为 \(0\) 到 \(n-1\),根节点为 \(0\))。
一个点的深度定义为这个节点到根的距离 \(+1\)。
设 \(dep[i]\) 表示点i的深度,\(LCA(i,j)\) 表示 \(i\) 与 \(j\) 的最近公共祖先。
有 \(m\) 次询问,每次询问给出 \(l\ r\ z\),求 \(\sum_{i=l}^r dep[LCA(i,z)]\)。
\(1\le n,m \le 50000\)
Solution
考虑暴力,枚举 \([l,r]\),计算 \(lca\) 的深度和。
那么怎么优化呢,由于深度就是到根的距离,所以可以把 \([l,r]\) 中每个点到根的链上的权值都 \(+1\),然后答案就是 \(z\) 到根的链的权值和。
因为对于每个点,与 \(z\) 的 \(lca\) 的深度就是 \(lca\) 到根的链上权值都 \(+1\)。
链上操作可以用树剖维护,复杂度 \(O(nm\log n)\),依然不行。
考虑怎么将 \(m\) 优化掉。
对于每个询问 \([l,r]\),可以利用差分的思想,答案为 \([1,r] - [1,l-1]\)。将 \(l,r\) 分别存进数组,并标记是左端点还是右端点,然后按 \(1\) 到 \(n\) 的顺序更新链上的权值,然后在询问处记下链上的和,最后相减即可。
Code
#include <bits/stdc++.h>
using namespace std;
template <typename T>
void read(T &x)
{
x = 0; char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
return;
}
template <typename T>
void write(T x)
{
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
const int N = 5e4 + 5;
const int p = 201314;
int n, m, fa[N];
vector <int> g[N];
struct Query
{
int pos, z;
bool flag;
int id;
friend bool operator < (Query x, Query y)
{
return x.pos < y.pos;
}
}q[N << 1];
int cnt;
int L[N], R[N];
int siz[N], dep[N], son[N];
void dfs1(int u)
{
dep[u] = dep[fa[u]] + 1, siz[u] = 1;
for(int i = 0; i < (int)g[u].size(); i++)
{
int v = g[u][i];
dfs1(v);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
return;
}
int top[N], id[N], tot;
void dfs2(int u, int tp)
{
top[u] = tp, id[u] = ++tot;
if(!son[u]) return;
dfs2(son[u], tp);
for(int i = 0; i < (int)g[u].size(); i++)
{
int v = g[u][i];
if(v != son[u]) dfs2(v, v);
}
return;
}
#define ls rt << 1
#define rs rt << 1 | 1
int sum[N << 2], tag[N << 2];
void pushup(int rt)
{
sum[rt] = sum[ls] + sum[rs];
}
void pushdown(int l, int r, int rt)
{
if(tag[rt])
{
int mid = (l + r) >> 1;
sum[ls] = (sum[ls] + tag[rt] * (mid - l + 1)) % p;
sum[rs] = (sum[rs] + tag[rt] * (r - mid)) % p;
tag[ls] = (tag[ls] + tag[rt]) % p;
tag[rs] = (tag[rs] + tag[rt]) % p;
tag[rt] = 0;
}
}
void upd(int L, int R, int v, int l, int r, int rt)
{
if(l > R || r < L) return;
if(L <= l && r <= R)
{
sum[rt] = (sum[rt] + v * (r - l + 1)) % p;
tag[rt] = (tag[rt] + v) % p;
return;
}
pushdown(l, r, rt);
int mid = (l + r) >> 1;
upd(L, R, v, l, mid, ls);
upd(L, R, v, mid + 1, r, rs);
pushup(rt);
}
int qry(int L, int R, int l, int r, int rt)
{
if(l > R || r < L) return 0;
if(L <= l && r <= R) return sum[rt];
pushdown(l, r, rt);
int mid = (l + r) >> 1;
return (qry(L, R, l, mid, ls) + qry(L, R, mid + 1, r, rs)) % p;
}
void update(int x, int val)
{
while(x)
{
upd(id[top[x]], id[x], val, 1, n, 1);
x = fa[top[x]];
}
return;
}
int query(int x)
{
int res = 0;
while(x)
{
res = (res + qry(id[top[x]], id[x], 1, n, 1)) % p;
x = fa[top[x]];
}
return res;
}
int main()
{
read(n), read(m);
for(int i = 2; i <= n; i++)
{
read(fa[i]), fa[i]++;
g[fa[i]].push_back(i);
}
dfs1(1), dfs2(1, 1);
for(int i = 1; i <= m; i++)
{
int l, r, z;
read(l), read(r), read(z);
l++, r++, z++;
q[++cnt] = (Query){l - 1, z, 0, i};
q[++cnt] = (Query){r, z, 1, i};
}
sort(q + 1, q + 1 + cnt);
int now = 1;
for(int i = 1; i <= cnt; i++)
{
while(now <= q[i].pos) update(now, 1), now++;
if(!q[i].flag) L[q[i].id] = query(q[i].z);
else R[q[i].id] = query(q[i].z);
}
for(int i = 1; i <= m; i++)
write((R[i] - L[i] + p) % p), puts("");
return 0;
}
$$A\ drop\ of\ tear\ blurs\ memories\ of\ the\ past.$$