Typesetting math: 100%

『LCA 树链剖分』

Parsnip·2019-07-17 21:24·318 次阅读

『LCA 树链剖分』

<更新提示>

<第一次更新>


<正文>

LCA#

Description#

给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根 的距离+1。
设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。 有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)]。 (即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)

Input Format#

第一行2个整数n q。 接下来n-1行,分别表示点1到点n-1的父节点编号。 接下来q行,每行3个整数l r z。

Output Format#

输出q行,每行表示一个询问的答案。每个答案对201314取模输出

Sample Input#

Copy
5 2 0 0 1 1 1 4 3 1 4 2

Sample Output#

Copy
8 5

解析#

容易发现一个询问可以拆成两个询问:

i=lrdep[LCA(i,z)]=i=1rdep[LCA(i,z)]i=1l1dep[LCA(i,z)]

都是前缀和形式的,方便我们处理。

对于一个前缀和询问,我们可以发现它的贡献具有一个实际意义:也就是求每一个iz公共祖先的个数。进一步,我们可以这样理解:把每一个节点i到根的路径上的点点权都加一,求z到更路径上的点权和。

我们都知道直接树上加链和查询链可以用树链剖分,但是各个询问之间好像相互有影响。

最简单的解决方法就是把询问离线。我们之前已经把询问转化为前缀和的形式了,那就把所有询问按照r排序,这样依次处理询问就没有影响了。

Code:

Copy
#include <bits/stdc++.h> using namespace std; const int N = 50020 , Mod = 201314; struct node { int l,r,tag; long long cnt; }; struct SegmentTree { node ver[N<<2]; #define l(p) ver[p].l #define r(p) ver[p].r #define cnt(p) ver[p].cnt #define tag(p) ver[p].tag inline void build(int p,int l,int r) { l(p) = l , r(p) = r , cnt(p) = tag(p) = 0; if ( l == r ) return; int mid = l + r >> 1; build( p<<1 , l , mid ); build( p<<1|1 , mid+1 , r ); } inline void update(int p) { cnt(p) = cnt(p<<1) + cnt(p<<1|1); } inline void spread(int p) { if ( tag(p) ) { tag(p<<1) = ( tag(p<<1) + tag(p) ) % Mod; tag(p<<1|1) = ( tag(p<<1|1) + tag(p) ) % Mod; cnt(p<<1) = ( cnt(p<<1) + 1LL * ( r(p<<1) - l(p<<1) + 1 ) * tag(p) % Mod ) % Mod; cnt(p<<1|1) = ( cnt(p<<1|1) + 1LL * ( r(p<<1|1) - l(p<<1|1) + 1 ) * tag(p) % Mod ) % Mod; tag(p) = 0; } } inline void modify(int p,int l,int r,int v) { if ( l <= l(p) && r >= r(p) ) { cnt(p) = ( cnt(p) + ( r(p) - l(p) + 1 ) * v % Mod ) % Mod; tag(p) = ( tag(p) + v ) % Mod; return; } spread( p ); int mid = l(p) + r(p) >> 1; if ( l <= mid ) modify( p<<1 , l , r , v ); if ( r > mid ) modify( p<<1|1 , l , r , v ); update( p ); } inline int query(int p,int l,int r) { if ( l <= l(p) && r >= r(p) ) return cnt(p); spread( p ); int mid = l(p) + r(p) >> 1 , res = 0; if ( l <= mid ) res = ( res + query( p<<1 , l , r ) ) % Mod; if ( r > mid ) res = ( res + query( p<<1|1 , l , r ) ) % Mod; return res; } }; SegmentTree Tree; struct edge { int ver,next; } e[N*2]; struct query { int r,z,id,f; } a[N*2]; int n,m,t,Head[N],tot; int fa[N],dep[N],son[N],size[N],top[N],id[N],cnt; long long ans[N]; inline void insert(int x,int y) { e[++t] = (edge){y,Head[x]} , Head[x] = t; e[++t] = (edge){x,Head[y]} , Head[y] = t; } inline int read(void) { int x = 0 , w = 0; char ch = ' '; while ( !isdigit(ch) ) w |= ch=='-' , ch = getchar(); while ( isdigit(ch) ) x = x*10 + ch-48 , ch = getchar(); return w ? -x : x; } inline void input(void) { n = read() , m = read(); for ( int i = 2 ; i <= n ; i++ ) insert( i , read() + 1 ); for ( int i = 1 ; i <= m ; i++ ) { int l = read() + 1 , r = read() + 1 , z = read() + 1; a[++tot] = (query){ r , z , i , 1 }; a[++tot] = (query){ l-1 , z , i , -1 }; } } inline void dfs1(int x,int f,int depth) { fa[x] = f , dep[x] = depth , size[x] = 1; int Max = -1; for ( int i = Head[x] ; i ; i = e[i].next ) { int y = e[i].ver; if ( y == f ) continue; dfs1( y , x , depth+1 ); size[x] += size[y]; if ( size[y] > Max ) Max = size[y] , son[x] = y; } } inline void dfs2(int x,int Top) { id[x] = ++cnt , top[x] = Top; if ( !son[x] ) return; else dfs2( son[x] , Top ); for ( int i = Head[x] ; i ; i = e[i].next ) { int y = e[i].ver; if ( y == fa[x] || y == son[x] ) continue; dfs2( y , y ); } } inline void modify_chain(int x,int y,int val) { while ( top[x] ^ top[y] ) { if ( dep[top[x]] < dep[top[y]] ) swap( x , y ); Tree.modify( 1 , id[top[x]] , id[x] , val ); x = fa[top[x]]; } if ( dep[x] > dep[y] ) swap( x , y ); Tree.modify( 1 , id[x] , id[y] , val ); } inline int query_chain(int x,int y) { int res = 0; while ( top[x] ^ top[y] ) { if ( dep[top[x]] < dep[top[y]] ) swap( x , y ); res = ( res + Tree.query( 1 , id[top[x]] , id[x] ) ) % Mod; x = fa[top[x]]; } if ( dep[x] > dep[y] ) swap( x , y ); return ( res + Tree.query( 1 , id[x] , id[y] ) ) % Mod; } inline bool compare(query p1,query p2) { return p1.r < p2.r; } inline void solve(void) { int p = 0; for ( int i = 1 ; i <= tot ; i++ ) { while ( p < a[i].r ) modify_chain( 1 , ++p , 1 ); ans[ a[i].id ] += a[i].f * query_chain( 1 , a[i].z ); ans[ a[i].id ] = ( ans[ a[i].id ] % Mod + Mod ) % Mod; } } int main(void) { input(); dfs1( 1 , 0 , 1 ); dfs2( 1 , 1 ); Tree.build( 1 , 1 , n ); sort( a+1 , a+tot+1 , compare ); solve(); for (int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }

<后记>

posted @   Parsnip  阅读(318)  评论(0)    收藏  举报
编辑推荐:
· 微服务架构学习与思考:微服务拆分的原则
· 记一次 .NET某云HIS系统 CPU爆高分析
· 如果单表数据量大,只能考虑分库分表吗?
· 一文彻底搞懂 MCP:AI 大模型的标准化工具箱
· 电商平台中订单未支付过期如何实现自动关单?
阅读排行:
· Cursor:一个让程序员“失业”的AI代码搭子
· 博客园2025新款「AI繁忙」系列T恤上架
· .NET 阻止Windows关机以及阻止失败的一些原因
· 航天二院校园招聘面试记录:二八三厂
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(6)
点击右上角即可分享
微信分享提示
目录