您的位置:首页 > 其它

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

2017-01-11 16:50 393 查看

=== ===

这里放传送门

=== ===

题解

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

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


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

离线处理有时候往往可以利用某种单调性,通过动态维护来放宽单个操作的时限。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息