【P4211 LNOI2014】LCA——树链剖分 +询问离线
(7.16晚)更完先在B站颓一会儿……
---------------------------------------------------------------
(以下为luogu题面)
题目描述
给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根的距离+1。 设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。 有q次询问,每次询问给出l r z,求∑(l≤i≤r dep[LCA(i,z)])
输入输出格式
输入格式:
第一行2个整数n q。 接下来n-1行,分别表示点1到点n-1的父节点编号。 接下来q行,每行3个整数l r z。
输出格式:
输出q行,每行表示一个询问的答案。每个答案对201314取模输出
说明
共5组数据,n与q的规模分别为10000,20000,30000,40000,50000。
需要说明的是,这道题的思路跟LCA的三种求法无关(树剖太暴力了,而且确实会用到树剖),也就是说没有丝毫的暴力分可拿。(掀桌)
从这道题中对于深度的定义可以发现,两个点LCA的深度等价于它们的公共祖先的数目。那么问题的本质是,每次给定一个点集,求集合中每个点与一个定点的祖先数目的累计。考虑这样的暴力处理:对于任取一个点u与所给定点z,我们可以先给u的每个祖先都打一层标记,然后询问从z到根有标记的点的数目。这个过程显然可以用树剖处理,我们在树剖序上架一个线段树支持区间修改、区间查询,每个这样的询问可以做到log^2n的复杂度。
如果我们对每次询问,都把点集中的每个点这样打一遍祖先的标记再查询z,总复杂度会多出一个q来,显然需要进一步优化。题中给出的点集有一个非常好的性质:点集中的点处于一段连续区间中。那么,我们可以把每个询问[l, r]差分为[1, l - 1]和[1, r]两个询问,那么需要维护的就只有形如[1, R]与每个z的关系了。
因此我们把询问离线差分后按右端点排序,从左向右扫描点集[1, n]并打标记,每遇到一个询问则查询给定z与根之间的标记数目,统计进询问数组即可。
代码:
- #include <cstdio>
- #include <iostream>
- #include <cstring>
- #include <algorithm>
- #define lowbit(i) (i & -i)
- #define maxn 50010
- #define mod 201314
- template <typename T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- return;
- }
- using namespace std;
- int head[maxn], top;
- struct E {
- int to, nxt;
- } edge[maxn];
- inline void insert(int u, int v) {
- edge[++top] = (E) {v, head[u]};
- head[u] = top;
- }
- int n, q, ans[maxn], qtp;
- struct Q {
- int r, z, id;
- bool op;
- friend bool operator < (Q a, Q b) {
- return a.r < b.r;
- }
- } ask[maxn << 1];
- /*namespace BIT {
- int bit[maxn];
- void modify(int x, int val) {
- for (int i = x; i <= n; i += lowbit(i))
- bit[i] += val;
- }
- int presum(int x) {
- int sum = 0;
- for (int i = x; i; i -= lowbit(i))
- sum = (sum + bit[i]) % mod;
- return sum;
- }
- }*/
- namespace Segment_tree {
- #define lc (nd<<1)
- #define rc ((nd<<1)|1)
- #define mid ((l+r)>>1)
- struct node {
- int val, len;
- friend node operator + (node a, node b) {
- return (node) {(a.val + b.val) % mod, a.len + b.len};
- }
- } seg[maxn << 2];
- int tag[maxn << 2];
- inline void update(int nd) {
- seg[nd] = seg[lc] + seg[rc];
- }
- inline void put_tag(int nd, int op) {
- seg[nd].val += op * seg[nd].len;
- tag[nd] += op;
- }
- inline void push_down(int nd) {
- put_tag(lc, tag[nd]);
- put_tag(rc, tag[nd]);
- tag[nd] = 0;
- }
- void build(int nd, int l, int r) {
- if (l == r) {
- seg[nd] = (node) {0, 1};
- return;
- }
- build(lc, l, mid);
- build(rc, mid + 1, r);
- update(nd);
- }
- void modify(int nd, int l, int r, int ql, int qr, int val) {
- if (l >= ql && r <= qr) {
- put_tag(nd, val);
- return;
- } else if (l > qr || r < ql)
- return;
- push_down(nd);
- modify(lc, l, mid, ql, qr, val);
- modify(rc, mid + 1, r, ql, qr, val);
- update(nd);
- return;
- }
- int query(int nd, int l, int r, int ql, int qr) {
- if (l >= ql && r <= qr)
- return seg[nd].val;
- if (l > qr || r < ql)
- return 0;
- push_down(nd);
- return (query(lc, l, mid, ql, qr) + query(rc, mid + 1, r, ql, qr)) % mod;
- }
- }
- namespace Div_tree { //树剖
- // using namespace BIT; //试图用树状数组维护区间修改的惨痛失败
- using namespace Segment_tree;
- int dfn[maxn], size[maxn], ftop[maxn], d[maxn], son[maxn], f[maxn];
- int tmr;
- void dfs1(int u, int pre) {
- f[u] = pre;
- d[u] = d[pre] + 1;
- size[u] = 1;
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- dfs1(v, u);
- size[u] += size[v];
- if (size[v] > size[son[u]])
- son[u] = v;
- }
- }
- void dfs2(int u, int tp) {
- dfn[u] = ++tmr;
- ftop[u] = tp;
- if (!son[u])
- return;
- dfs2(son[u], tp);
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- if (v != son[u])
- dfs2(v, v);
- }
- }
- void Mrange(int u, int v, int val) {
- while (ftop[u] != ftop[v]) {
- if (d[ftop[u]] < d[ftop[v]])
- swap(u, v);
- modify(1, 1, n, dfn[ftop[u]], dfn[u], val);
- u = f[ftop[u]];
- }
- if (d[u] < d[v]) swap(u, v);
- modify(1, 1, n, dfn[v], dfn[u], val);
- return;
- }
- int Qrange(int u, int v) {
- int sum = 0;
- while (ftop[u] != ftop[v]) {
- if (d[ftop[u]] < d[ftop[v]])
- swap(u, v);
- sum = (sum + query(1, 1, n, dfn[ftop[u]], dfn[u])) % mod;
- u = f[ftop[u]];
- }
- if (d[u] < d[v]) swap(u, v);
- sum += query(1, 1, n, dfn[v], dfn[u]);
- return sum % mod;
- }
- } using namespace Div_tree;
- void init() {
- build(1, 1, n);
- dfs1(1, 0);
- dfs2(1, 1);
- }
- int main() {
- read(n), read(q);
- int u, v, z;
- for (int i = 2; i <= n; ++i)//编号+1
- read(u), ++u, insert(u, i);
- for (int i = 1; i <= q; ++i) {
- read(u), read(v), read(z);
- ask[++qtp] = (Q) {u, z+1, i, 0}; //拆询问
- ask[++qtp] = (Q) {v+1, z+1, i, 1};
- }
- sort(ask + 1, ask + qtp + 1);
- init();
- int i = 0, j = 1;
- while (j <= qtp) {
- while (i < ask[j].r)
- Mrange(1, ++i, 1);
- if (ask[j].op)
- ans[ask[j].id] += Qrange(1, ask[j].z); //按标记统计进答案
- else ans[ask[j].id] -= Qrange(1, ask[j].z);
- ++j;
- }
- for (int i = 1; i <= q; ++i)
- printf("%d\n", (ans[i]+mod)%mod);
- return 0;
- }