【BZOJ3626】LCA(树链剖分)

传送门

LCA

I think

    %%%:( 清华爷题解 )考虑这样的一种暴力,我们把 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),均可以完成任务。至此,题目已经被我们完美解决。
    实现:将询问按l排序,设置“断点”后for循环扫过去中间添加while循环即可实现。
    个人觉得这真是优美的一道题,而且1A很开心>-<

Code

/**************************************************************
    Problem: 3626
    User: Etta
    Language: C++
    Result: Accepted
    Time:1824 ms
    Memory:8332 kb
****************************************************************/

#include<cstdio>
#include<algorithm>
typedef long long LL;

const int sm = 5e4+10;
const int mod = 201314;

int N,Q,cnt,tot;
int to[sm<<1],nxt[sm<<1],hd[sm];
int Fa[sm],dp[sm],sn[sm],sz[sm],tp[sm],pl[sm];
LL C[sm<<2],Mk[sm<<2];
struct Stop {
    int p,num; bool flag;
}stp[sm<<1];
struct Ans {
    int z; LL res[2];
}ans[sm];
void Add(int u,int v) {
    to[++tot]=v,nxt[tot]=hd[u],hd[u]=tot;
    to[++tot]=u,nxt[tot]=hd[v],hd[v]=tot;
} 
bool cmp(Stop x,Stop y) { return x.p<y.p; }
namespace Tree {
    void Dfsa(int x,int fa) {
        dp[x]=dp[fa]+1,sn[x]=0;
        sz[x]=1,Fa[x]=fa;
        for(int i=hd[x];i;i=nxt[i]) 
            if(to[i]!=fa) {
                Dfsa(to[i],x);
                sz[x]+=sz[to[i]];
                if(sz[sn[x]]<sz[to[i]])
                    sn[x]=to[i];
            }
    }
    void Dfsb(int x,int top) {
        pl[x]=++tot,tp[x]=top;
        if(sn[x]) Dfsb(sn[x],top);
        for(int i=hd[x];i;i=nxt[i])
            if(to[i]!=Fa[x]&&to[i]!=sn[x])
                Dfsb(to[i],to[i]);
    }

    void Pd(int rt,int l,int r,int m) {
        Mk[rt<<1]+=Mk[rt],Mk[rt<<1|1]+=Mk[rt];
        C[rt<<1]+=1ll*(m-l+1)*Mk[rt],C[rt<<1|1]+=1ll*(r-m)*Mk[rt];
        Mk[rt]=0;
    } 
    void Update(int rt,int l,int r,int a,int b) {
        if(a<=l&&r<=b) { C[rt]+=r-l+1,Mk[rt]++; return ; }
        int m=(l+r)>>1;
        if(Mk[rt]) Pd(rt,l,r,m);
        if(a<=m) Update(rt<<1,l,m,a,b);
        if(b> m) Update(rt<<1|1,m+1,r,a,b);
        C[rt]=C[rt<<1]+C[rt<<1|1];
    }
    LL Query(int rt,int l,int r,int a,int b) {
        if(a<=l&&r<=b) return C[rt];
        int m=(l+r)>>1;LL Ans=0;
        if(Mk[rt]) Pd(rt,l,r,m);
        if(a<=m) Ans+=Query(rt<<1,l,m,a,b);
        if(b> m) Ans+=Query(rt<<1|1,m+1,r,a,b);
        C[rt]=C[rt<<1]+C[rt<<1|1];
        return Ans;
    }
    void TUpdate(int x) {
        while(tp[x]!=tp[1]) {
            Update(1,1,N,pl[tp[x]],pl[x]);
            x=Fa[tp[x]];
        }
        Update(1,1,N,pl[1],pl[x]);
    }
    LL TQuery(int Z) {
        LL Ans=0;
        while(tp[Z]!=tp[1]) {
            Ans+=Query(1,1,N,pl[tp[Z]],pl[Z]);
            Z=Fa[tp[Z]];
        }
        Ans+=Query(1,1,N,pl[1],pl[Z]);
        return Ans;
    }
}
int main() {
    using namespace Tree;
    scanf("%d%d",&N,&Q);
    for(int i=2,x;i<=N;++i)
        scanf("%d",&x),Add(i,x+1);
    for(int i=1,l,r;i<=Q;++i) {
        scanf("%d%d%d",&l,&r,&ans[i].z),ans[i].z++;
        stp[++cnt].num=i,stp[cnt].p=l,stp[cnt].flag=0;
        stp[++cnt].num=i,stp[cnt].p=r+1,stp[cnt].flag=1;
    }
    std::sort(stp+1,stp+cnt+1,cmp);
    tot=0,Dfsa(1,0),Dfsb(1,1);
    for(int i=1,j=1;j<=cnt;++j) {
        while(i<=stp[j].p) TUpdate(i),++i;//先更新后询问
        ans[stp[j].num].res[stp[j].flag]=TQuery(ans[stp[j].num].z);
    }
    for(int i=1;i<=Q;++i) 
        printf("%lld\n",(ans[i].res[1]-ans[i].res[0])%mod);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值