【LNOI 2014】LCA

mac2022-06-30  20

传送门


problem

给出一个 n n n 个节点的有根树(编号为 0 0 0 n − 1 n-1 n1,根节点为 0 0 0)。一个点的深度定义为这个节点到根的距离 + 1 +1 +1

d e p [ i ] dep[i] dep[i] 表示点 i i i 的深度, LCA ( i , j ) \texttt{LCA}(i,j) LCA(i,j) 表示 i i i j j j 的最近公共祖先。

q q q 次询问,每次询问给出 l    r    z l\; r\; z lrz,求 ∑ i = l r d e p [ LCA ( i , z ) ] \sum\limits_{i=l}^rdep[\texttt{LCA}(i,z)] i=lrdep[LCA(i,z)]

数据范围: n , q ≤ 50000 n,q\le50000 n,q50000


solution

对于一组询问 l    r    z l\;r\;z lrz,对于 ∀ i ∈ [ l , r ] \forall i\in[l,r] i[l,r],设 LCA ( i , z ) = g i \texttt{LCA}(i,z)=g_i LCA(i,z)=gi

首先比较显然的是,所有的 g i g_i gi 应该在 1 1 1 z z z 的路径上。考虑 d e p [ g i ] dep[g_i] dep[gi] 的含义是 1 1 1 g i g_i gi 路径上的点数,于是对于 ∀ i ∈ [ l , r ] \forall i\in[l,r] i[l,r],我们对 1 1 1 g i g_i gi 这条链加 1 1 1,询问答案的时候 1 1 1 z z z 的链和就是答案。

但是,我们貌似没办法快速找到所有的 g i g_i gi,这时换个思路,直接在 1 1 1 i i i 这条链加 1 1 1,发现效果是一样的。

可是,对于每个询问都暴力加点、暴力清空的话,复杂度又上去了,怎么办呢?

我们把拆开,即 a n s [ l , r ] = a n s [ 1 , r ] − a n s [ 1 , l − 1 ] ans_{[l,r]}=ans_{[1,r]}-ans_{[1,l-1]} ans[l,r]=ans[1,r]ans[1,l1] ,左端点都变成 1 1 1,然后按右端点从小到大的顺序加点。这样做的好处就是,不用每次询问完就清空线段树,可以在上一次的基础上完成此次询问。

时间复杂度 O ( n log ⁡ 2 n ) O(n\log ^2n) O(nlog2n)


code

#include<cstdio> #include<vector> #include<cstring> #include<algorithm> #define N 50005 #define P 201314 using namespace std; int n,q,cnt,tot; int dep[N],fa[N],son[N],Size[N],top[N],pos[N],ans[N],tag[N<<2],sum[N<<2]; struct query{int id,r,z,flag;}Q[N<<1]; vector<int>e[N]; bool operator<(const query &p,const query &q) {return p.r<q.r;} int add(int x,int y) {return x+y>=P?x+y-P:x+y;} int dec(int x,int y) {return x-y< 0?x-y+P:x-y;} int mul(int x,int y) {return 1ll*x*y%P;} void dfs1(int x){ Size[x]=1; for(int &to:e[x]){ fa[to]=x,dep[to]=dep[x]+1; dfs1(to),Size[x]+=Size[to]; if(Size[son[x]]<Size[to]) son[x]=to; } } void dfs2(int x,int tp){ top[x]=tp,pos[x]=++tot; if(son[x]) dfs2(son[x],tp); for(int &to:e[x]) if(to!=son[x]) dfs2(to,to); } void addit(int root,int len,int num){ tag[root]=add(tag[root],num); sum[root]=add(sum[root],mul(num,len)); } void pushdown(int root,int l,int r,int mid){ if(!tag[root]) return; addit(root<<1,mid-l+1,tag[root]); addit(root<<1|1,r-mid,tag[root]); tag[root]=0; } #define mid ((l+r)>>1) void Modify(int root,int l,int r,int x,int y){ if(l>=x&&r<=y){ return addit(root,r-l+1,1); } pushdown(root,l,r,mid); if(x<=mid) Modify(root<<1,l,mid,x,y); if(y> mid) Modify(root<<1|1,mid+1,r,x,y); sum[root]=add(sum[root<<1],sum[root<<1|1]); } int Query(int root,int l,int r,int x,int y){ if(l>=x&&r<=y) return sum[root]; pushdown(root,l,r,mid); if(y<=mid) return Query(root<<1,l,mid,x,y); if(x> mid) return Query(root<<1|1,mid+1,r,x,y); return add(Query(root<<1,l,mid,x,y),Query(root<<1|1,mid+1,r,x,y)); } #undef mid void Add(int x){ while(top[x]!=1){ Modify(1,1,n,pos[top[x]],pos[x]),x=fa[top[x]]; } Modify(1,1,n,1,pos[x]); } int Ask(int x){ int ans=0; while(top[x]!=1){ ans=add(ans,Query(1,1,n,pos[top[x]],pos[x])),x=fa[top[x]]; } return add(ans,Query(1,1,n,1,pos[x])); } int main(){ scanf("%d%d",&n,&q); for(int i=2,x;i<=n;++i) scanf("%d",&x),e[x+1].push_back(i); dep[1]=1,dfs1(1),dfs2(1,1); for(int i=1,l,r,z;i<=q;++i){ scanf("%d%d%d",&l,&r,&z); Q[++cnt]=(query){i,l,z+1,0}; Q[++cnt]=(query){i,r+1,z+1,1}; } sort(Q+1,Q+cnt+1); int now=0; for(int i=1;i<=cnt;++i){ while(now<Q[i].r) Add(++now); if(Q[i].flag) ans[Q[i].id]=add(ans[Q[i].id],Ask(Q[i].z)); else ans[Q[i].id]=dec(ans[Q[i].id],Ask(Q[i].z)); } for(int i=1;i<=q;++i) printf("%d\n",ans[i]); return 0; }
最新回复(0)