BZOJ[3626][LNOI2014]LCA 树链剖分+线段树

33 篇文章 0 订阅
21 篇文章 0 订阅

题目链接http://www.lydsy.com/JudgeOnline/problem.php?id=3626

Description

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

Input

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

Output

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

Sample Input

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

Sample Output

8
5

HINT

共5组数据,n与q的规模分别为10000,20000,30000,40000,50000。

题目是LCA,内容跟LCA半毛钱关系都没有....

我们先考虑两个点 a,b ,求他们的LCA可以将 a 到根节点都打上+1标记,再看b到根的路径中有多少标记,标记数则为 a b的LCA深度

那么对于[l,r]区间的所有点与z的LCA深度和,我们当然不能给每一个 i[l,r] 打到根的标记啦..
我们可以考虑考虑可不可以离线来做
发现每个询问都是一段段连续的区间 lr ,发现这个东西是可以用前缀和来计算的! anslr=ansransl1 ,我们可以初步考虑求出每一个 ansi ,我们可以记录每一个需要求出的 ansi zi ,并对其排个序,从1到n一个个向根打标记,期间如果有 ansi 则查询一下 zi 到根有多少标记,记录一下,就可以求出所有答案了…

树上区间加和区间求和可以用树剖+线段树来实现…

代码如下:

#include<algorithm>
#include<ctype.h>
#include<cstdio>
#define N 50020
#define MOD 201314
using namespace std;
char xB[1<<15],*xS=xB,*xTT=xB;
#define getc() (xS==xTT&&(xTT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xTT)?0:*xS++)
#define isd(c) (c>='0'&&c<='9')
inline int read(){
    char xchh; int xaa=0;
    while(xchh=getc(),!isd(xchh));(xaa=xchh-'0');
    while(xchh=getc(),isd(xchh))xaa=xaa*10+xchh-'0'; return xaa;
}
int n,m,x,Top,T,l,r,z,pre;
int ans[N][2];
int fir[N],top[N],tree[N],size[N],dep[N],fa[N],son[N];
struct Edge{
    int to,nex;
    Edge(int _=0,int __=0):to(_),nex(__){}
}nex[N];
struct Node{
    int l,r,sum,v;
}a[N*5];
struct Problem{
    int num,x,z,t;
    Problem(int _=0,int __=0,int ___=0,int ____=0):num(_),x(__),z(___),t(____){}
}s[N*2];
inline void add(int x,int y){
    nex[++Top]=Edge(y,fir[x]);
    fir[x]=Top;
}
inline void addquery(int i,int x,int k,int t){
    s[++Top]=Problem(i,x,k,t);
}
inline bool cmp(Problem a,Problem b){return a.x<b.x;}
void dfs1(int x,int Fa,int Dep){
    fa[x]=Fa;dep[x]=Dep;
    size[x]=1;
    for(int i=fir[x];i;i=nex[i].nex){
        if(nex[i].to==Fa) continue;
        dfs1(nex[i].to,x,Dep+1);
        size[x]=size[x]+size[nex[i].to];
        if(size[nex[i].to]>size[son[x]]) son[x]=nex[i].to;
    }
}//树剖1
void dfs2(int x,int Top){
    tree[x]=++T;top[x]=Top;
    if(!son[x]) return;
    dfs2(son[x],Top);
    for(int i=fir[x];i;i=nex[i].nex){
        if(nex[i].to==fa[x] || nex[i].to==son[x]) continue;
        dfs2(nex[i].to,nex[i].to);
    }
}//树剖2
inline void Update(int k){
    a[k].sum=a[k*2].sum+a[k*2+1].sum;
}
void maketree(int l,int r,int k){
    a[k].l=l;a[k].r=r;a[k].v=0;
    if(l==r){
        a[k].sum=0;
        return;
    }
    int mid=(l+r)>>1;
    maketree(l,mid,2*k);maketree(mid+1,r,2*k+1);
    Update(k);
}//线段树
inline void Pushdown(int k){
    if(!a[k].v) return;
    a[2*k].v+=a[k].v;a[2*k+1].v+=a[k].v;
    a[2*k].sum+=(a[2*k].r-a[2*k].l+1)*a[k].v;
    a[2*k+1].sum+=(a[2*k+1].r-a[2*k+1].l+1)*a[k].v;
    a[k].v=0;
}//线段树下传标记
int Query_Sum(int x,int y,int k){
    if(a[k].l>=x && a[k].r<=y){
        return a[k].sum;
    }
    Pushdown(k);
    int mid=a[k].l+a[k].r>>1,t;
    if(x>mid) t=Query_Sum(x,y,2*k+1);
    else if(y<=mid) t=Query_Sum(x,y,2*k);
    else t=Query_Sum(x,y,2*k+1)+Query_Sum(x,y,2*k);
    Update(k);
    Pushdown(k);
    return t;
}//线段树区间和
void Add(int x,int y,int k,int v){
    if(a[k].l>=x && a[k].r<=y){
        a[k].v+=v;
        a[k].sum+=(a[k].r-a[k].l+1)*v;
        return;
    }
    Pushdown(k);
    int mid=a[k].l+a[k].r>>1;
    if(x>mid) {Add(x,y,2*k+1,v);Pushdown(k);Update(k);return;}
    if(y<=mid) {Add(x,y,2*k,v);Pushdown(k);Update(k);return;}
    Add(x,y,2*k+1,v);Add(x,y,2*k,v);
    Update(k);
    Pushdown(k);
}//线段树区间加
inline void Add_Tree(int x,int y,int v){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        Add(tree[top[x]],tree[x],1,v);
        x=fa[top[x]];
    }
    if(tree[x]>tree[y]) swap(x,y);
    Add(tree[x],tree[y],1,v);
}//树剖树上加
inline int Query_Tree(int x,int y){
    int sum=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        sum=sum+Query_Sum(tree[top[x]],tree[x],1);
        x=fa[top[x]];
    }
    if(tree[x]>tree[y]) swap(x,y);
    sum=sum+Query_Sum(tree[x],tree[y],1);
return sum;
}//树剖树上查询
int main(){
    n=read();m=read();
    for(int i=2;i<=n;i++){
        x=read()+1;
        add(x,i);
    }
    dfs1(1,0,1);dfs2(1,1);
    maketree(1,n,1);
    Top=0;
    for(int i=1;i<=m;i++){
        l=read()+1;r=read()+1;z=read()+1;
        addquery(i,l-1,z,0);addquery(i,r,z,1);//将每个询问记录下来,这四个东西分别表示 属于哪个询问 到多少的前缀和 让谁往上查询 是哪种询问(1表示r,0表示l-1)
    }
    sort(s+1,s+Top+1,cmp);//排遍序
    for(int i=1,pre=1;pre<=Top;i++){
    while(s[pre].x==0) pre++;//将所有询问0的前缀和都除去
        Add_Tree(i,1,1);
        while(s[pre].x==i) ans[s[pre].num][s[pre].t]=Query_Tree(s[pre].z,1),pre++;//记录答案
    }
    for(int i=1;i<=m;i++) printf("%d\n",(ans[i][1]-ans[i][0])%MOD);//输出答案
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值