[LNOI2014]LCA 解题报告
对于一棵 \(n\) 个节点的树,给出 \(m\) 次询问,每次给出 \(l,r,x\) ,求 \(\sum\limits_{i=l}^r depth(LCA(i,x))\) 。
\(n,m\le 5\times 10^4 , 1\le l\le r\le n , x\le n\)
一道不错的题目。
说明有时候用一些其他的做法求一个简单的东西也可以帮助思考。
对于我,求LCA都是用倍增来做的,但这题要求区间对于一个节点x的LCA的深度之和,如果只是在想用倍增的手法是做不出来的。
由于只是深度,并不需要得出切实的LCA,这就给我们提供了一个很好的方向。
考虑另外一种求LCA深度的做法。
如果要求x和y的LCA的深度,可以先将x到根的路径全部赋值为1,再在统计y到根的路径就可以得到LCA的深度了。
这个做法有一个很好的性质,那就是可加性。
所以只要把区间里的所有节点到根的路径都加1,最后在统计节点x到根的路径的值就行了。
首先链上的修改和求和可以用树链剖分做到 \(\mathcal{O(\log n)}\) ,考虑如何把区间给弄掉。
由于可加性和可减性,通过差分就可以将区间变为前缀相减,然后将查询拆成两个,离线排序一下,从1号节点不断修改到n号节点,如果遇到了对应的前缀查询,直接查询存储即可。
总效率 \(\mathcal{O(n\log n+m\log n)}\) ,精细实现可以做到 \(\mathcal{O(n\log n)}\) 。
然而实际上这个将LCA深度转换为链上求和的方法也是一种差分思想,所以就出现了加强版的旧词。
#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
const int M=5e4+5,JYY=201314;
void swap(int &x,int &y){ x^=y^=x^=y; }
int min(int x,int y){ return x<y?x:y; }
int max(int x,int y){ return x>y?x:y; }
int n,q,fa[M],de[M],ans[M],pre[M];
struct Ques{
int x,z,id,zf;
}Q[M<<1];
int read(){
int x=0,y=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') y=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*y;
}
int tot=0,first[M];
struct Edge{
int nxt,to;
}e[M<<1];
void add(int x,int y){
e[++tot].nxt=first[x];
first[x]=tot;
e[tot].to=y;
}
struct Tree{
int sum,len,lazy;
}tr[M<<2];
void pushup(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u){
if(!tr[u].lazy) return ;
tr[u<<1].sum+=tr[u<<1].len*tr[u].lazy;
tr[u<<1].lazy+=tr[u].lazy;
tr[u<<1|1].sum+=tr[u<<1|1].len*tr[u].lazy;
tr[u<<1|1].lazy+=tr[u].lazy;
tr[u].lazy=0;
}
void build(int u,int l,int r){
tr[u].sum=tr[u].lazy=0;
tr[u].len=r-l+1;
if(l==r) return ;
int mid=(l+r)>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
return ;
}
void change(int u,int l,int r,int L,int R,int x){
if(l>R||r<L) return ;
if(l>=L&&r<=R){
tr[u].sum+=tr[u].len*x;
tr[u].lazy+=x;
return ;
}
pushdown(u);
int mid=(l+r)>>1;
change(u<<1,l,mid,L,R,x);
change(u<<1|1,mid+1,r,L,R,x);
pushup(u);
return ;
}
int query(int u,int l,int r,int L,int R){
if(l>R||r<L) return 0;
if(l>=L&&r<=R) return tr[u].sum;
pushdown(u);
int mid=(l+r)>>1;
return query(u<<1,l,mid,L,R)+query(u<<1|1,mid+1,r,L,R);
}
int num=0,St[M],En[M],son[M],top[M],dfn[M],size[M];
void dfs1(int u,int fa){
de[u]=de[fa]+1;
size[u]=1;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue ;
dfs1(v,u);
size[u]+=size[v];
if(size[son[u]]<size[v]){
son[u]=v;
}
}
}
void dfs2(int u,int fa,int tp){
St[u]=++num;
top[u]=tp;
if(son[u]) dfs2(son[u],u,tp);
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa||v==son[u]) continue ;
dfs2(v,u,v);
}
En[u]=num;
}
void Change(int x,int d){
while(top[x]!=1){
change(1,1,n,St[top[x]],St[x],d);
x=fa[top[x]];
}
change(1,1,n,St[1],St[x],d);
return ;
}
int Query(int x){
int res=0;
while(top[x]!=1){
res=(res+query(1,1,n,St[top[x]],St[x]))%JYY;
x=fa[top[x]];
}
res=(res+query(1,1,n,St[1],St[x]))%JYY;
return res;
}
bool cmp(Ques x,Ques y){
return x.x<y.x;
}
int main(){
n=read(),q=read();
for(int i=2;i<=n;i++){
fa[i]=read()+1;
add(fa[i],i);
}
dfs1(1,0);
dfs2(1,0,1);
build(1,1,n);
for(int i=1;i<=n;i++){
pre[i]=(pre[i-1]+de[i])%JYY;
}
for(int i=1;i<=q;i++){
int l=read()+1,r=read()+1,z=read()+1;
Q[i*2-1].x=l-1;
Q[i*2-1].z=z;
Q[i*2-1].zf=-1;
Q[i*2-1].id=i;
Q[i*2].x=r;
Q[i*2].z=z;
Q[i*2].zf=1;
Q[i*2].id=i;
}
sort(Q+1,Q+q*2+1,cmp);
int now=0;
for(int i=1;i<=2*q;i++){
while(now<Q[i].x){
now++;
Change(now,1);
}
ans[Q[i].id]=(ans[Q[i].id]+Query(Q[i].z)*Q[i].zf+JYY)%JYY;
}
for(int i=1;i<=q;i++){
printf("%d\n",ans[i]);
}
}