BZOJ 3626: [LNOI2014]LCA [树链剖分 离线|主席树]

3626: [LNOI2014]LCA

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 2050  Solved: 817
[Submit][Status][Discuss]

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

第一行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。 

Source

数据已加强 by saffah

 


 

orz gconeice

显然,暴力求解的复杂度是无法承受的。
考虑这样的一种暴力,我们把 z 到根上的点全部打标记,对于 l 到 r 之间的点,向上搜索到第一个有标记的点求出它的深度统计答案。观察到,深度其实就是上面有几个已标记了的点(包括自身)。所以,我们不妨把 z 到根的路径上的点全部 +1,对于 l 到 r 之间的点询问他们到根路径上的点权和。仔细观察上面的暴力不难发现,实际上这个操作具有叠加性,且可逆。也就是说我们可以对于 l 到 r 之间的点 i,将 i 到根的路径上的点全部 +1, 转而询问 z 到根的路径上的点(包括自身)的权值和就是这个询问的答案。把询问差分下,也就是用 [1, r] − [1, l − 1] 来计算答案,那么现在我们就有一个明显的解法。从 0 到 n − 1 依次插入点 i,即将 i 到根的路径上的点全部+1。离线询问答案即可。我们现在需要一个数据结构来维护路径加和路径求和,显然树链剖分或LCT 均可以完成这个任务。树链剖分的复杂度为 O((n + q)· log n · log n),LCT的复杂度为 O((n + q)· log n),均可以完成任务。至此,题目已经被我们完美解决。

z的LCA只能在z到根的路径上,统计到根的路径上每个点被作为LCA几次

很巧妙的转换,[l,r]所有点到根路径+1,然后询问z到根路径权值

显然树链剖分

多组询问,考虑将询问差分,[1, r] − [1, l − 1],然后离线询问的两个端点,每加到一个now将l-1或者r是now到询问都问一下

 

//
//  main.cpp
//  bzoj4196
//
//  Created by Candy on 2017/1/2.
//  Copyright © 2017年 Candy. All rights reserved.
//

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define lc o<<1
#define rc o<<1|1
#define m ((l+r)>>1)
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
const int N=1e5+5,MOD=201314;
typedef long long ll;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int n,Q,u,l,r;
char s[20];
struct ques{
    int z,al,ar;
}q[N];
struct que{
    int x,id,isl;
    bool operator <(const que &r)const{return x<r.x;}
}a[N];
int p;
struct edge{
    int v,ne,c,f;
}e[N<<1];
int cnt,h[N];
inline void ins(int u,int v){
    cnt++;
    e[cnt].v=v;e[cnt].ne=h[u];h[u]=cnt;
}

int deep[N],fa[N],tid[N],tot,top[N],mx[N],size[N];
void dfs(int u){
    size[u]=1;
    for(int i=h[u];i;i=e[i].ne){
        int v=e[i].v;
        if(v==fa[u]) continue;
        fa[v]=u;deep[v]=deep[u]+1;
        dfs(v);
        size[u]+=size[v];
        if(size[v]>size[mx[u]]) mx[u]=v;
    }
}
void dfs(int u,int anc){
    if(!u) return;
    tid[u]=++tot;
    top[u]=anc;
    dfs(mx[u],anc);
    for(int i=h[u];i;i=e[i].ne)
        if(e[i].v!=mx[u]&&e[i].v!=fa[u]) dfs(e[i].v,e[i].v);
}

struct node{
    int sum,add;
}t[N<<2];
inline void merge(int o){
    t[o].sum=t[lc].sum+t[rc].sum;
}
inline void paint(int o,int l,int r,int d){
    t[o].sum+=d*(r-l+1);
    t[o].add+=d;
}
inline void pushDown(int o,int l,int r){
    if(t[o].add){
        paint(lson,t[o].add);
        paint(rson,t[o].add);
        t[o].add=0;
    }
}
inline void segAdd(int o,int l,int r,int ql,int qr,int d){//printf("add %d %d %d\n",o,l,r);
    if(ql<=l&&r<=qr) paint(o,l,r,d);
    else{
        pushDown(o,l,r);
        if(ql<=m) segAdd(lson,ql,qr,d);
        if(m<qr) segAdd(rson,ql,qr,d);
        merge(o);
    }
}
inline int segQue(int o,int l,int r,int ql,int qr){//printf("que %d %d %d %d %d\n",o,l,r,ql,qr);
    if(ql<=l&&r<=qr) return t[o].sum;
    else{
        pushDown(o,l,r);
        int ans=0;
        if(ql<=m) ans+=segQue(lson,ql,qr);
        if(m<qr) ans+=segQue(rson,ql,qr);
        return ans;
    }
}
void build(int o,int l,int r){
    if(l==r) paint(o,l,r,0);
    else{
        build(lson);
        build(rson);
    }
}

void add(int x,int y,int d){
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        segAdd(1,1,n,tid[top[x]],tid[x],d);
        x=fa[top[x]];
    }
    if(tid[x]>tid[y]) swap(x,y);
    segAdd(1,1,n,tid[x],tid[y],d);
}
int query(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        ans+=segQue(1,1,n,tid[top[x]],tid[x]);ans%=MOD;
        x=fa[top[x]];
    }
    if(tid[x]>tid[y]) swap(x,y);
    ans+=segQue(1,1,n,tid[x],tid[y]);ans%=MOD;
    return ans;
}
int main(){
    n=read();Q=read();
    for(int i=2;i<=n;i++) u=read()+1,ins(u,i);
    dfs(1);dfs(1,1);
    build(1,1,n);
    for(int i=1;i<=Q;i++){
        l=read();r=read()+1;q[i].z=read()+1;
        a[++p].x=l;a[p].id=i;a[p].isl=1;
        a[++p].x=r;a[p].id=i;a[p].isl=0;
    }
    sort(a+1,a+1+p);
    int now=1;
    for(int i=1;i<=p;i++){//printf("a %d %d %d\n",a[i].x,a[i].id,a[i].isl);
        while(now<=a[i].x) add(1,now,1),now++;
        int _=a[i].id;
        if(a[i].isl) q[_].al=query(1,q[_].z);
        else q[_].ar=query(1,q[_].z);
    }
    for(int i=1;i<=Q;i++) printf("%d\n",(q[i].ar-q[i].al+MOD)%MOD);
}

 

在线的话可以用主席树,在dfs序上建主席树

这样每个线段树与历史版本的差距是一个节点,也就是一个点到根的路径,有logn段,每段有logn个新开节点,复杂度log^2n

好麻烦还要带标记啊

 [2017-01-04 00:01:51]

 我太弱了,不是M就是R,不玩了

posted @ 2017-01-03 22:38  Candy?  阅读(323)  评论(0编辑  收藏  举报