题解 [LNOI2014]LCA 树链剖分+离线差分

题解 [LNOI2014]LCA

题目描述

题目链接
在这里插入图片描述

具体做法与心路历程

这道题搁了有一段时间,趁周六晚上没有划水的时间里写了这道题。这道题。。也算得上是套路吧。

具体做法

这种问题每次询问一堆点的贡献一般是把贡献进行某种转换,使其与它到祖先这条链扯上关系。一般是能够变成区间修改和区间查询。

我们看题目要求的是 d e p t h [ L C A u , v ] depth[LCA_{u,v}] depth[LCAu,v],这个可以直接转换成把 u u u到根的路径上的权值 + 1 +1 +1,然后查询 v v v到根节点上的点权和即可。

这么转换后可以考虑没每次的询问 l , r , z l,r,z l,r,z,把 l , r l,r l,r的点权加到链上然后查询 z z z到根的链上权值和。可以用树链剖分+线段树来维护。

对于每个询问的时间复杂度都为 O ( n l o g n 2 ) O(nlogn^2) O(nlogn2)。总的时间复杂度为 O ( n q l o g n 2 ) O(nqlogn^2) O(nqlogn2),接受不了。

看到题目的 l , r l,r l,r我们可以想到差分,即把答案看成 1 1 1 ~ l − 1 l-1 l1 1 1 1 ~ r r r的贡献差。

我们可以想到把询问离线下来后差分,然后枚举 i i i 1 1 1 ~ n n n,依次把 i i i到根的链上的权值 + 1 +1 +1,同时处理掉差分在 i i i点的询问,最后输出答案即可。

C o d e \mathcal{Code} Code

边听音乐边码代码效率奇高,20min打完一遍过~

/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019年11月02日 星期六 21时59分00秒
*******************************/
#include<cstdio>
#include<algorithm>
#include<vector>

using namespace std;

struct IO{
    template<typename T>
    IO & operator>>(T&res)
    {
        T q=1;char ch;
        while((ch=getchar())<'0' or ch>'9')if(ch=='-')q=-q;
        res=(ch^48);
        while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48);
        res*=q;
        return *this;
    }
}cin;

struct edge{
    int to,next;
    edge(int a=0,int b=0):to(a),next(b){}
};

struct Query{
    int pos,val,id;
    Query(int a=0,int b=0,int c=0):pos(a),val(b),id(c){}
};

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

int n,q,head[maxn],cnt,top[maxn],size[maxn],son[maxn],seg[maxn],f[maxn],depth[maxn],ans[maxn];
edge e[maxn<<1];
vector<Query>V[maxn];

void add(int u,int v)
{
    e[++cnt]=edge(v,head[u]);
    head[u]=cnt;
}

/*{{{线段树*/

namespace SegmentTree{

    int tr[maxn*4],lazy[maxn*4];

    void update(int k)
    {
        tr[k]=tr[k<<1]+tr[k<<1|1];
    }

    void work(int k,int l,int r,int val)
    {
        lazy[k]+=val;
        tr[k]+=(r-l+1)*val;
    }

    void pushdown(int k,int l,int r,int mid)
    {
        if(!lazy[k]) return;
        work(k<<1,l,mid,lazy[k]);
        work(k<<1|1,mid+1,r,lazy[k]);
        lazy[k]=0;
    }

    void modify(int k,int l,int r,int x,int y,int val)
    {
        if(l>=x && r<=y) return work(k,l,r,val);
        if(l>y  ||  r<x) return ;
        int mid=(l+r)>>1;
        pushdown(k,l,r,mid);
        modify(k<<1,l,mid,x,y,val);
        modify(k<<1|1,mid+1,r,x,y,val);
        update(k);
    }

    int query(int k,int l,int r,int x,int y)
    {
        if(l>=x && r<=y) return tr[k];
        if(l>y  ||  r<x) return 0;
        int mid=(l+r)>>1;
        pushdown(k,l,r,mid);
        return (query(k<<1,l,mid,x,y)+query(k<<1|1,mid+1,r,x,y));
    }

};

/*}}}*/

/*{{{树链剖分*/

#define v e[i].to

void dfs(int now)
{
    depth[now]=depth[f[now]]+1;
    size[now]=1;
    for(int i=head[now];i;i=e[i].next)
        if(v!=f[now])
        {
            f[v]=now;
            dfs(v);
            size[now]+=size[v];
            if(size[v]>size[son[now]])
                son[now]=v;
        }
}

void dfs(int now,int tp)
{
    seg[now]=++seg[0];
    top[now]=tp;
    if(son[now])
        dfs(son[now],tp);
    for(int i=head[now];i;i=e[i].next)
        if(v!=f[now] && v!=son[now])
            dfs(v,v);
}

void change(int x,int y,int val)
{
    while(top[x]!=top[y])
    {
        if(depth[top[x]]<depth[top[y]]) swap(x,y);
        SegmentTree::modify(1,1,seg[0],seg[top[x]],seg[x],val);
        x=f[top[x]];
    }
    if(depth[x]>depth[y]) swap(x,y);
    SegmentTree::modify(1,1,seg[0],seg[x],seg[y],val);
}

int ask(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if(depth[top[x]]<depth[top[y]]) swap(x,y);
        ans=(ans+SegmentTree::query(1,1,seg[0],seg[top[x]],seg[x]))%mod;
        x=f[top[x]];
    }
    if(depth[x]>depth[y]) swap(x,y);
    ans=(ans+SegmentTree::query(1,1,seg[0],seg[x],seg[y]))%mod;
    return ans;
}

#undef v

/*}}}*/

int main()
{
    /*freopen("p4211.in","r",stdin);*/
    /*freopen("p4211.out","w",stdout);*/
    cin>>n>>q;
    int v;
    for(int i=2;i<=n;i++)
    {
        cin>>v;
        add(v+1,i);
    }
    dfs(1);
    dfs(1,1);
    int l,r,z;
    for(int i=1;i<=q;i++)
    {
        cin>>l>>r>>z;
        l++,r++,z++;
        V[l-1].push_back(Query(z,-1,i));
        V[r].push_back(Query(z,1,i));
    }
    int res=0;
    for(int i=1;i<=n;i++)
    {
        change(1,i,1);
        for(int j=0;j<V[i].size();j++)
        {
            res=ask(1,V[i][j].pos);
            ans[V[i][j].id]=(ans[V[i][j].id]+V[i][j].val*res+mod)%mod;
        }
    }
    for(int i=1;i<=q;i++)
        printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值