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

来源:互联网 发布:shadow web 黑暗网络 编辑:程序博客网 时间:2024/05/01 03:17

=== ===

这里放传送门

=== ===

题解

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

对于一个点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;}

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

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

1 0
原创粉丝点击