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

=== ===

这里放传送门

=== ===

题解

感觉这题思路还是比较神的。。

对于一个点u,我们要查它跟某个给定点z的LCA的深度,那么如果我们把u这个点到根节点的路径全都打上+1的标记,那么z到根的路径上的标记数目就是u和z的LCA的深度。这个东西画一画就感觉比较科学了。。如果是对于两个点u和v要求跟z的LCA的深度,那就把u和v都打上到根的一串标记然后查z到根的标记个数就可以了。。。

那我们可以离线询问,离线完了以后就可以把每个询问拆成两个,因为这个东西显然是满足前缀和相减的性质。那问题就变成了有一堆[1,n]这样的区间要求回答这个询问。结合上面那种方法,我们可以把询问们都按照右端点从小到大排序,然后从1号点开始一个一个往树上打一串标记。每次当走到i这个点的时候说明1-i所有点的标记已经都被加进去了,那么这个时候就可以处理[1,i]这样的询问了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,Q,p[50010],a[50010],next[50010],top[50010],size[50010],son[50010],fa[50010],tot,cnt;
int deep[50010],sum[200010],dlt[200010],ans[50010][2],cur[50010],ptr,w[50010],qcnt;
void add(int x,int y){
    tot++;a[tot]=y;next[tot]=p[x];p[x]=tot;
}
struct question{
    int r,z,id,iid;
}q[100010];
int comp(question a,question b){
    return a.r<b.r;
}
void addquery(int r,int z,int id,int iid){
    ++qcnt;q[qcnt].r=r;q[qcnt].z=z;q[qcnt].id=id;q[qcnt].iid=iid;
}
void dfs(){
    int u=1;
    bool flag;
    while (true){
        flag=false;
        if (deep[u]==0){
            deep[u]=deep[fa[u]]+1;
            size[u]=1;son[u]=0;cur[u]=p[u];
        }
        for (int i=cur[u];i!=0;i=next[i]){
            int v=a[i];
            cur[u]=next[i];fa[v]=u;
            u=v;flag=true;break;
        }
        if (flag==false)
          if (u==1) break;
          else{
              int v=fa[u];
              size[v]+=size[u];
              if (size[son[v]]<size[u]) son[v]=u;
              u=fa[u];
          }
    }
}
void dfs_again(){
    int u=1,tp=1;
    bool flag;
    while (true){
        flag=false;
        if (top[u]==0){
            top[u]=tp;w[u]=++cnt;cur[u]=p[u];
            if (son[u]!=0){u=son[u];continue;}
        }
        for (int i=cur[u];i!=0;i=next[i]){
            cur[u]=next[i];u=a[i];
            tp=a[i];flag=true;break;
        }
        if (flag==false)
          if (u==1) break;
          else u=fa[u];
    }
}
void update(int i){
    sum[i]=sum[i<<1]+sum[(i<<1)+1];
}
void pushdown(int i,int l,int r){
    if (dlt[i]!=0){
        int mid=(l+r)>>1;
        sum[i<<1]+=(mid-l+1)*dlt[i];
        sum[(i<<1)+1]+=(r-mid)*dlt[i];
        dlt[i<<1]+=dlt[i];dlt[(i<<1)+1]+=dlt[i];
        dlt[i]=0;
    }
}
void add(int i,int l,int r,int left,int right){
    if (left<=l&&right>=r){
        sum[i]+=r-l+1;dlt[i]++;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(i,l,r);
    if (left<=mid) add(i<<1,l,mid,left,right);
    if (right>mid) add((i<<1)+1,mid+1,r,left,right);
    update(i);
}
void change(int x,int y){
    while (top[x]!=top[y]){
        if (deep[top[x]]<deep[top[y]]) swap(x,y);
        add(1,1,n,w[top[x]],w[x]);
        x=fa[top[x]];
    }
    if (deep[x]>deep[y]) swap(x,y);
    add(1,1,n,w[x],w[y]);
}
int ask(int i,int l,int r,int left,int right){
    if (left<=l&&right>=r) return sum[i];
    int mid=(l+r)>>1,ans=0;
    pushdown(i,l,r);
    if (left<=mid) ans+=ask(i<<1,l,mid,left,right);
    if (right>mid) ans+=ask((i<<1)+1,mid+1,r,left,right);
    return ans;
}
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+=ask(1,1,n,w[top[x]],w[x]);
        x=fa[top[x]];
    }
    if (deep[x]>deep[y]) swap(x,y);
    ans+=ask(1,1,n,w[x],w[y]);
    return ans;
}
int main()
{
    scanf("%d%d",&n,&Q);
    for (int i=2;i<=n;i++){
        int fa;scanf("%d",&fa);++fa;
        add(fa,i);
    }
    dfs();dfs_again();
    for (int i=1;i<=Q;i++){
        int l,r,z;
        scanf("%d%d%d",&l,&r,&z);
        ++l;++r;++z;
        addquery(l-1,z,i,0);
        addquery(r,z,i,1);//把每个询问拆成两个
    }
    sort(q+1,q+qcnt+1,comp);
    ptr=1;
    while (q[ptr].r==0){
        ans[q[ptr].id][q[ptr].iid]=0;
        ++ptr;
    }
    for (int i=1;i<=n;i++){
        change(1,i);
        while (q[ptr].r==i){
            ans[q[ptr].id][q[ptr].iid]=query(1,q[ptr].z);
            ++ptr;
        }
    }
    for (int i=1;i<=Q;i++)
      printf("%d\n",(ans[i][1]-ans[i][0])%201314);
    return 0;
}

偏偏在最后出现的补充说明

离线处理有时候往往可以利用某种单调性,通过动态维护来放宽单个操作的时限。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值