[LNOI2014] LCA
题目链接:
题目分析
考虑暴力
枚举每个询问,从\(l\)到\(r\)依次把每个节点抓出来和\(z\)求\(LCA\)并累加\(dep\),复杂度\(O(n^2log(n))\)
考虑怎么优化,有两种入手点
-
一次性快速求出一大堆\(LCA\)
(不会,告辞) -
或者能集中统计\(dep\)
走后面一条路吧,前面一条是什么鬼啊不会啊\(QAQ\)
假装我们不会倍增不会树剖不会\(tarjan\)不会一些奇奇怪怪的做法求\(LCA\)
给我们两个点,我们要怎么求\(LCA\)?
一种可行的方案是一个点\(u\)跳到根节点,沿途染色并点权\(+1\),那么另一个点\(v\)向上跳,遇到第一个染过色的点时,这个点就是他们的\(LCA\),并且从\(v\)到根节点的路径点权和就是\(LCA\)的深度。
考虑对于每一个询问,我们仍然遍历所有的询问点,并暴力跳到根节点,沿途点权\(+1\),最后跳\(z\)并统计此次询问答案。
恭喜我们把\(O(n^2log(n))\)的算法优化到\(O(n^3)\)
考虑继续优化,显然从每一个询问点到根节点增加点权的操作可以用树剖的链加单次\(O(log^2(n))\)处理,每次询问后清空\(segT\),得到新暴力\(O(n^2log^2(n))\)
发现有很多重复的加点权操作,考虑把询问离线下来,把一个询问拆成两个询问区间\(1 - (l - 1)\)和\(1-r\),此时询问左端点为\(1\),按右端点排序,然后把排序之后的询问扫一遍,依次把上次询问的右端点到这次询问的左端点之间的点都\(modify\)到线段树上去,然后再对于\(z\)查询即可。
这样省去了枚举\(q\)的复杂度,总时间复杂度为\(O(nlog^2(n))\)
#include<bits/stdc++.h>
#define N (100000 + 10)
using namespace std;
inline int read() {
int cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
return cnt * f;
}
const int mod = 201314;
int n, q, x;
int ans1[N], ans2[N];
int first[N], nxt[N], to[N], tot;
void add(int x, int y) {nxt[++tot] = first[x], first[x] = tot, to[tot] = y;}
int dep[N], fa[N], top[N], siz[N], son[N], num[N], idx;
struct node_q{
int pos, time, z;
bool flag;
}Q[N];
bool cmp (node_q a, node_q b) {return a.pos < b.pos;}
void dfs_(int x, int father) {
fa[x] = father, siz[x] = 1, dep[x] = dep[father] + 1;
for (register int i = first[x]; i; i = nxt[i]) {
int v = to[i];
if (v == father) continue;
dfs_(v, x); siz[x] += siz[v];
if (siz[son[x]] < siz[v]) son[x] = v;
}
}
void dfs__(int x, int tp) {
top[x] = tp, num[x] = ++idx;
if (son[x]) dfs__(son[x], tp);
for (register int i = first[x]; i; i = nxt[i]) {
int v = to[i];
if (num[v]) continue;
dfs__(v, v);
}
}
struct node {
int l, r, sum, tag;
#define l(p) tree[p].l
#define r(p) tree[p].r
#define sum(p) tree[p].sum
#define tag(p) tree[p].tag
}tree[N << 2];
void pushup(int p) {sum(p) = sum(p << 1) + sum(p << 1 | 1);}
void pushadd(int p, int d) {sum(p) += (r(p) - l(p) + 1) * d, tag(p) += d;}
void pushdown(int p) {pushadd(p << 1, tag(p)), pushadd(p << 1 | 1, tag(p)); tag(p) = 0;}
void build(int p, int l, int r) {
l(p) = l, r(p) = r;
if (l == r) return;
int mid = (l + r) >> 1;
build (p << 1, l, mid);
build (p << 1 | 1, mid + 1, r);
}
void modify(int p, int l, int r, int d) {
if (l <= l(p) && r >= r(p)) {pushadd(p, d); return;}
pushdown(p);
int mid = (l(p) + r(p)) >> 1;
if (l <= mid) modify(p << 1, l, r, d);
if (r > mid) modify(p << 1 | 1, l, r, d);
pushup(p);
}
int query(int p, int l, int r) {
if (l <= l(p) && r >= r(p)) return sum(p);
pushdown(p);
int ans = 0;
int mid = (l(p) + r(p)) >> 1;
if (l <= mid) ans += query(p << 1, l, r);
if (r > mid) ans += query(p << 1 | 1, l, r);
return ans;
}
void Modify(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
modify(1, num[top[u]], num[u], 1);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
modify(1, num[v], num[u], 1);
}
int Query(int u, int v) {
int ans = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
ans += query(1, num[top[u]], num[u]);
u = fa[top[u]];
}
if (dep[u] < dep[v]) swap(u, v);
ans += query(1, num[v], num[u]);
return ans;
}
int main() {
// freopen("1.in", "r", stdin);
n = read(), q = read();
for (register int i = 2; i <= n; ++i) {x = read(); add(x + 1, i), add(i, x + 1);}
for (register int i = 1; i <= q; ++i) {
Q[i * 2 - 1].pos = read(), Q[i * 2 - 1].time = Q[i * 2].time = i;
Q[i * 2].pos = read() + 1, Q[i * 2 - 1].z = Q[i * 2].z = read() + 1;
Q[i * 2 - 1].flag = 0, Q[i * 2].flag = 1;
}
dfs_(1, 0), dfs__(1, 1), build (1, 1, n);
q <<= 1;
sort (Q + 1, Q + q + 1, cmp);
int now = 0;
for (register int i = 1; i <= q; ++i) {
while (now < Q[i].pos) Modify(1, ++now);
x = Q[i].time;
if (Q[i].flag) ans1[x] = Query(1, Q[i].z);
else ans2[x] = Query(1, Q[i].z);
}
for (register int i = 1; i <= (q >> 1); ++i) printf("%d\n", (ans1[i] - ans2[i] + mod) % mod);
return 0;
}