洛谷 P4211 [LNOI2014]LCA
洛谷 P4211 [LNOI2014]LCA
题目描述
给出一个 nn 个节点的有根树(编号为 00 到 n-1n−1,根节点为 00)。
一个点的深度定义为这个节点到根的距离 +1+1。
设 dep[i]dep[i] 表示点i的深度,LCA(i,j)LCA(i,j) 表示 ii 与 jj 的最近公共祖先。
有 qq 次询问,每次询问给出 l\ r\ zl r z,求 \sum_{i=l}^r dep[LCA(i,z)]∑i=lrdep[LCA(i,z)] 。
输入格式
第一行 22 个整数,n, qn,q。
接下来 n-1n−1 行,分别表示点 11 到点 n-1n−1 的父节点编号。
接下来 qq 行,每行 33 个整数,l, r, zl,r,z。
输出格式
输出 qq 行,每行表示一个询问的答案。每个答案对 201314201314 取模输出。
题解:
题目很简洁。一看就是好题。
首先,我们可以得到,任意节点和已知节点的LCA一定在已知节点到根节点的这条路径上。
我们可以深入思考:
深度是什么?深度就是当前点上面(包括自己)还有几个节点。那么我们完全可以把深度统计变成路径统计。既然deep[LCA]是LCA到根节点的距离,那么,求和的时候,我们完全可以对于\([l,r]\)区间,都把根节点到当前节点的路径的节点们权值全部加一。那么处理完毕之后,只需要跑目标节点\(z\)到树根的路径和就行。
芜湖,链上修改+查询。树链剖分架线段树。
分析复杂度:这样的复杂度,一次是\(O(n\log^2)\)的,\(n\)次就是\(O(n^2\log^2)\)。
这个数据范围是\(O(n\log^2)\)的数据范围,那么我们想着如何优化掉一个n。
我们发现,\(n^2\log^2\)算法多出来的n消耗在重构线段树上,那么,如果我们连续处理每个询问呢?想到离线区间排序之后连续处理。但是这样的处理无法同时维护两个端点的变化。于是我们尝试着把一个区间端点固定为1.因为以上的性质满足区间减法,也就是\([1,r]-[1,l)\),所以可以通过这样的优化,只跑一遍修改即可。优化了一个n。
具体实现详见代码:
#include<cstdio>
#include<algorithm>
#define int long long
#define lson pos<<1
#define rson pos<<1|1
using namespace std;
const int maxn=5*1e5+10;
const int mod=201314;
char *p1,*p2,buf[100000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int read()
{
int x=0,f=1;
char ch=nc();
while(ch<48||ch>57)
f=-1,ch=nc();
while(ch>=48&&ch<=57)
x=x*10+ch-48,ch=nc();
return x*f;
}
int n,m;
int fa[maxn],ans[maxn];
int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
int cnt;
int deep[maxn],size[maxn],son[maxn],top[maxn],id[maxn],num;
int tree[maxn<<2],lazy[maxn<<2];
struct node
{
bool flag;
int id,r,z;
}q[maxn<<1];
bool cmp(node a,node b)
{
return a.r<b.r;
}
void add(int x,int y)
{
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs1(int x,int f)
{
deep[x]=deep[f]+1;
size[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==f)
continue;
dfs1(y,x);
size[x]+=size[y];
if(!son[x]||size[y]>size[son[x]])
son[x]=y;
}
}
void dfs2(int x,int t)
{
top[x]=t;
id[x]=++num;
if(!son[x])
return;
dfs2(son[x],t);
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa[x]||y==son[x])
continue;
dfs2(y,y);
}
}
void mark(int pos,int l,int r,int k)
{
tree[pos]=(tree[pos]+(r-l+1)*k)%mod;
lazy[pos]+=k;
}
void pushdown(int pos,int l,int r)
{
int mid=(l+r)>>1;
mark(lson,l,mid,lazy[pos]);
mark(rson,mid+1,r,lazy[pos]);
lazy[pos]=0;
}
void update(int pos,int l,int r,int x,int y)
{
int mid=(l+r)>>1;
if(x<=l && r<=y)
{
mark(pos,l,r,1);
return;
}
if(lazy[pos])
pushdown(pos,l,r);
if(x<=mid)
update(lson,l,mid,x,y);
if(y>mid)
update(rson,mid+1,r,x,y);
tree[pos]=(tree[lson]+tree[rson])%mod;
}
void upd_chain(int x,int y)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])
swap(x,y);
update(1,1,n,id[top[x]],id[x]);
x=fa[top[x]];
}
if(deep[x]<deep[y])
swap(x,y);
update(1,1,n,id[y],id[x]);
}
int query(int pos,int l,int r,int x,int y)
{
int ret=0;
int mid=(l+r)>>1;
if(x<=l && r<=y)
return tree[pos]%mod;
if(lazy[pos])
pushdown(pos,l,r);
if(x<=mid)
ret=(ret+query(lson,l,mid,x,y))%mod;
if(y>mid)
ret=(ret+query(rson,mid+1,r,x,y))%mod;
return ret%mod;
}
int q_chain(int x,int y)
{
int ret=0;
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])
swap(x,y);
ret=(ret+query(1,1,n,id[top[x]],id[x]))%mod;
x=fa[top[x]];
}
if(deep[x]>deep[y])
swap(x,y);
ret=(ret+query(1,1,n,id[x],id[y]))%mod;
return ret%mod;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=2;i<=n;i++)
{
scanf("%lld",&fa[i]);
fa[i]++;
add(fa[i],i);
add(i,fa[i]);
}
dfs1(1,0);
dfs2(1,1);
for(int i=1;i<=m;i++)
{
int l,r,z;
scanf("%lld%lld%lld",&l,&r,&z);
r++,z++;
q[++cnt].flag=0,q[cnt].id=i,q[cnt].r=l,q[cnt].z=z;
q[++cnt].flag=1,q[cnt].id=i,q[cnt].r=r,q[cnt].z=z;
}
sort(q+1,q+cnt+1,cmp);
q[0].r=0;
for(int i=1;i<=cnt;i++)
{
for(int j=q[i-1].r+1;j<=q[i].r;j++)
upd_chain(1,j);
if(!q[i].flag)
ans[q[i].id]=(ans[q[i].id]-q_chain(1,q[i].z)+mod)%mod;
else
ans[q[i].id]=(ans[q[i].id]+q_chain(1,q[i].z))%mod;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}