[LNOI2014]LCA——树链剖分——多点LCA深度和问题

模板:题目链接
进阶:题目链接

题意:

给定一棵 n n n个点的树。 q q q次询问,每次询问给出三个值 l , r , z l,r,z l,r,z,要求出 ∑ i = l r d e p ( L C A ( i , z ) ) \sum_{i=l}^{r}dep(LCA(i,z)) i=lrdep(LCA(i,z))
( n < = 1 e 5 , q < = 1 e 5 ) (n<=1e5,q<=1e5) (n<=1e5,q<=1e5)

思路:

考虑求点 a a a和点 b b b L C A LCA LCA深度,我们可以把点 a a a r o o t root root路径上的点权都加 1 1 1,然后查询点 b b b r o o t root root的路径和就是他们的 L C A LCA LCA深度了。反过来修改 b b b r o o t root root的路径,查 a a a r o o t root root的路径和也是一样的。

那么考虑多个点和单个点的 L C A LCA LCA深度和,可以把每个点到 r o o t root root的路径都加上 1 1 1,然后查询单个点到 r o o t root root的路径和。

这样就变成了树链修改和查询的操作,就可以用树链剖分维护了。

但是这题不能每次询问都进行一次 o ( ( r − l ) l o g 2 n ) o((r-l)log^2n) o((rl)log2n)的修改,所以要离线求前缀和,再拿前缀和减一下就行了。总的复杂度 o ( ( n + q ) l o g 2 n ) o((n+q)log^{2}n) o((n+q)log2n)

代码:

#include
using namespace std;
typedef long long ll;
const ll mod=201314;
typedef pair<ll,ll> P;
ll tree[1000005],laz[1000005];
void push_down(ll k,ll l,ll r)
{
    ll mid=(l+r)/2;
    tree[2*k+1]+=laz[k]*(mid-l+1);
    laz[2*k+1]+=laz[k];
    tree[2*k+2]+=laz[k]*(r-mid);
    laz[2*k+2]+=laz[k];
    laz[k]=0;
}
ll query(ll k,ll l,ll r,ll x,ll y)
{
    if(x<=l&&r<=y)
    {
        return tree[k];
    }
    push_down(k,l,r);
    ll ans=0,mid=(l+r)/2;
    if(x<=mid)ans+=query(2*k+1,l,mid,x,y);
    if(y>=mid+1)ans+=query(2*k+2,mid+1,r,x,y);
    return ans;
}
void update(ll k,ll l,ll r,ll x,ll y,ll a)
{
    if(x<=l&&r<=y)
    {
        tree[k]+=a*(r-l+1);
        laz[k]+=a;
        return ;
    }
    push_down(k,l,r);
    ll mid=(l+r)/2;
    if(x<=mid)update(2*k+1,l,mid,x,y,a);
    if(y>=mid+1)update(2*k+2,mid+1,r,x,y,a);
    tree[k]=tree[2*k+1]+tree[2*k+2];
}

vector<ll>v[100005];

ll dep[100005],fa[100005],siz[100005],son[100005];
void dfs1(ll x,ll pre)
{
    siz[x]=1;
    dep[x]=dep[pre]+1;
    fa[x]=pre;
    for(ll i=0;i<v[x].size();i++)
    {
        ll to=v[x][i];
        if(to!=pre)
        {
            dfs1(to,x);
            siz[x]+=siz[to];
        }
    }
    son[x]=x;
    for(ll i=0;i<v[x].size();i++)
    {
        ll to=v[x][i];
        if(to!=pre)
        {
            if(son[x]==x||siz[son[x]]<siz[to])son[x]=to;
        }
    }
}

ll id[100005],idx,top[100005];
void dfs2(ll x,ll pre)
{
    if(son[pre]==x)top[x]=top[pre];
    else top[x]=x;
    id[x]=++idx;
    if(son[x]!=x)dfs2(son[x],x);
    for(ll i=0;i<v[x].size();i++)
    {
        ll to=v[x][i];
        if(to!=pre&&to!=son[x])
        {
            dfs2(to,x);
        }
    }
}

ll n,m;
void add(ll x,ll val)
{
    while(top[x]!=1)
    {
        update(1,1,n,id[top[x]],id[x],val);
        x=fa[top[x]];
    }
    update(1,1,n,id[top[x]],id[x],val);
}
ll get(ll x)
{
    if(x==0)return 0;
    ll ans=0;
    while(top[x]!=1)
    {
        ans+=query(1,1,n,id[top[x]],id[x]);
        x=fa[top[x]];
    }
    ans+=query(1,1,n,id[top[x]],id[x]);
    return ans;
}

map<ll,ll>mp[100005];
vector<P>ve;
struct Q
{
    ll l,r,z;
}q[200005];


int main()
{
    scanf("%lld%lld",&n,&m);
    for(ll i=2;i<=n;i++)
    {
        ll a;
        scanf("%lld",&a);
        a++;
        v[a].push_back(i);
        v[i].push_back(a);
    }
    dfs1(1,0);
    dfs2(1,0);
    for(int i=1;i<=m;i++)
    {
        ll l,r,z;
        scanf("%lld%lld%lld",&l,&r,&z);
        l++;r++;z++;
        q[i]=Q{l,r,z};
        ve.push_back(P(l-1,z));
        ve.push_back(P(r,z));
    }
    sort(ve.begin(),ve.end());
    int cnt=1;
    for(int i=0;i<ve.size();i++)
    {
        while(cnt<=n&&cnt<=ve[i].first)
        {
            add(cnt,1);
            cnt++;
        }
        mp[ve[i].first][ve[i].second]=get(ve[i].second)%mod;
    }
    for(int i=1;i<=m;i++)
    {
        ll l=q[i].l,r=q[i].r,z=q[i].z;
        ll ans=mp[r][z]-mp[l-1][z];
        printf("%lld\n",(ans%mod+mod)%mod);
    }
    return 0;
}

你可能感兴趣的:(树链剖分)