[BZOJ3626][LNOI2014]LCA(离线+差分+树链剖分)

211 篇文章 0 订阅
23 篇文章 0 订阅

题目描述

传送门

题解

首先把询问lr拆成两个询问。
利用差分的思想,每次查询区间[1,i]与zlca的和,然后用r的值减去l-1的值就是答案。
查询区间[1,i]与zlca的和,可以将1-i所有的点到根的路径上的点都+1,然后查询z到根的路径上所有点的权值和即为答案。其实这就是深度的表示。
那么离线询问之后排序,时间复杂度为O(nlogn)

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=5e4+5;
const int max_e=max_n*2;
const int max_tree=max_n*4;
const int Mod=201314;

int n,q,fa,l,r,z,N,u,t,cnt;
int tot,point[max_n],nxt[max_e],v[max_e];
int size[max_n],top[max_n],h[max_n],father[max_n],son[max_n],num[max_n];
int sum[max_tree],delta[max_tree];
struct hp{int pt,z,id,f;}ask[max_e];
int ans[max_n][3];

inline void addedge(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
inline int cmp(hp a,hp b)
{
    return a.pt<b.pt;
}
inline void dfs_1(int x,int fa,int dep)
{
    size[x]=1; h[x]=dep; father[x]=fa;
    int maxson=0;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
        {
            dfs_1(v[i],x,dep+1);
            size[x]+=size[v[i]];
            if (size[v[i]]>maxson)
            {
                maxson=size[v[i]];
                son[x]=v[i];
            }
        }
}
inline void dfs_2(int x,int fa)
{
    if (son[fa]==x) top[x]=top[fa];
    else top[x]=x;
    num[x]=++N;
    if (son[x]) dfs_2(son[x],x);
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa&&v[i]!=son[x])
            dfs_2(v[i],x);
}
inline void update(int now)
{
    sum[now]=sum[now<<1]+sum[now<<1|1];
}
inline void pushdown(int now,int l,int r,int mid)
{
    if (delta[now])
    {
        sum[now<<1]+=delta[now]*(mid-l+1);
        delta[now<<1]+=delta[now];
        sum[now<<1|1]+=delta[now]*(r-mid);
        delta[now<<1|1]+=delta[now];
        delta[now]=0;
    }
}
inline void interval_change(int now,int l,int r,int lrange,int rrange,int v)
{
    int mid=(l+r)>>1;
    if (lrange<=l&&r<=rrange)
    {
        sum[now]+=v*(r-l+1);
        delta[now]+=v;
        return;
    }
    pushdown(now,l,r,mid);
    if (lrange<=mid)
        interval_change(now<<1,l,mid,lrange,rrange,v);
    if (mid+1<=rrange)
        interval_change(now<<1|1,mid+1,r,lrange,rrange,v);
    update(now);
}
inline int query(int now,int l,int r,int lrange,int rrange)
{
    int mid=(l+r)>>1,ans=0;
    if (lrange<=l&&r<=rrange) return sum[now];
    pushdown(now,l,r,mid);
    if (lrange<=mid)
        ans+=query(now<<1,l,mid,lrange,rrange);
    if (mid+1<=rrange)
        ans+=query(now<<1|1,mid+1,r,lrange,rrange);
    return ans;
}
int main()
{
    scanf("%d%d",&n,&q);
    for (int i=2;i<=n;++i)
    {
        scanf("%d",&fa);
        fa++;
        addedge(fa,i);
    }
    dfs_1(1,0,1);
    dfs_2(1,0);
    for (int i=1;i<=q;++i)
    {
        scanf("%d%d%d",&l,&r,&z);
        ++l; ++r; ++z;
        ask[++cnt].pt=l-1; ask[cnt].z=z; ask[cnt].id=i; ask[cnt].f=1;
        ask[++cnt].pt=r; ask[cnt].z=z; ask[cnt].id=i; ask[cnt].f=2;
    }
    sort(ask+1,ask+cnt+1,cmp);
    for (int i=1;i<=cnt;++i)
    {
        for (int j=ask[i-1].pt+1;j<=ask[i].pt;++j)
        {
            u=1; t=j;
            int f1=top[u],f2=top[t];
            while (f1!=f2)
            {
                if (h[f1]<h[f2])
                {
                    swap(f1,f2);
                    swap(u,t);
                }
                interval_change(1,1,N,num[f1],num[u],1);
                u=father[f1];
                f1=top[u];
            }
            if (num[u]>num[t]) swap(u,t);
            interval_change(1,1,N,num[u],num[t],1);

        }
        u=1; t=ask[i].z;
        int f1=top[u],f2=top[t];
        while (f1!=f2)
        {
            if (h[f1]<h[f2])
            {
                swap(f1,f2);
                swap(u,t);
            }
            ans[ask[i].id][ask[i].f]+=query(1,1,N,num[f1],num[u]);
            u=father[f1];
            f1=top[u];
        }
        if (num[u]>num[t]) swap(u,t);
        ans[ask[i].id][ask[i].f]+=query(1,1,N,num[u],num[t]);
    }
    for (int i=1;i<=q;++i)
        printf("%d\n",(ans[i][2]-ans[i][1]+Mod)%Mod);
}

总结

想到了离线,想到了差分,想到了标记一个查询一个,但是没有想到深度标记的表示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值