树上技巧

1.与 \(LCA\) 深度有关的查询

两个点的 \(LCA\) 的深度可以转化为,将其中一个点到根的路径上每个点的点权均加 \(1\) 然后查询另一个点到根的点权和

P4211 [LNOI2014] LCA

Description

给出一个 \(n\) 个节点的有根树(编号为 \(0\)\(n-1\),根节点为 \(0\))。

一个点的深度定义为这个节点到根的距离 \(+1\)

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

\(m\) 次询问,每次询问给出 \(l, r, z\),求 \(\sum_{i=l}^r dep[\operatorname{LCA}(i,z)]\)

\(1\le n\le 50000,1\le m\le 50000\)

Solution

先离线,对查询差分,然后按照 \(1~n\) 的顺序路径加并同时处理查询。

路径加使用树剖,时间复杂度 \(O(nlogn)\)

#include<bits/stdc++.h>
#define pb push_back
#define N 50005
#define mid ((l+r)>>1)
#define mod 201314
using namespace std;
int n,m,f[N],ans[N],rk[N],tot,top[N],dep[N],son[N],siz[N];
int tag[N<<2],sum[N<<2];
struct query{int num,x,f;};
vector<query>q[N];
vector<int>t[N];
inline void dfs(int u){
	dep[u]=dep[f[u]]+1,siz[u]=1,son[u]=n;
	for(int v:t[u])dfs(v),siz[u]+=siz[v],siz[v]>siz[son[u]]?son[u]=v:0;
}
inline void dfs(int u,int tp){
	rk[u]=++tot,top[u]=tp;
	if(son[u]!=n)dfs(son[u],tp);
	for(int v:t[u])if(v!=son[u])dfs(v,v);
}
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
inline void F(int p,int l,int r,int k){
	sum[p]+=(r-l+1)*k;
	tag[p]+=k;
}
inline void push_down(int p,int l,int r){
	if(!tag[p])return;
	F(ls(p),l,mid,tag[p]);
	F(rs(p),mid+1,r,tag[p]);
	tag[p]=0; 
}
inline void push_up(int p){sum[p]=sum[ls(p)]+sum[rs(p)];}
inline void addsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return F(p,l,r,1),void();
	push_down(p,l,r);
	if(L<=mid)addsum(ls(p),l,mid,L,R);
	if(R>mid)addsum(rs(p),mid+1,r,L,R);
	push_up(p);
} 
inline int getsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return sum[p];
	push_down(p,l,r);
	int res=0;
	if(L<=mid)res+=getsum(ls(p),l,mid,L,R);
	if(R>mid)res+=getsum(rs(p),mid+1,r,L,R);
	return res;
}
inline int op(int u){
	int res=0;
	for(;~u;u=f[top[u]])res+=getsum(1,1,n,rk[top[u]],rk[u]);
	return res;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m,f[0]=-1;
	for(int i=1;i<n;i++)cin>>f[i],t[f[i]].pb(i);
	for(int i=0,l,r,z;i<m;i++)
		cin>>l>>r>>z,q[r].pb({i,z,1}),~(l-1)?q[l-1].pb({i,z,-1}):void();
	dfs(0),dfs(0,0);
	for(int i=0;i<n;i++){
		for(int j=i;~j;j=f[top[j]])addsum(1,1,n,rk[top[j]],rk[j]);
		for(auto j:q[i])ans[j.num]+=j.f*op(j.x);
	}
	for(int i=0;i<m;i++)cout<<ans[i]%mod<<'\n';
}

P5305 [GXOI/GZOI2019] 旧词

Description

给定一棵 \(n\) 个点的有根树,节点标号 \(1 \sim n\)\(1\) 号节点为根。

给定常数 \(k\)

给定 \(Q\) 个询问,每次询问给定 \(x,y\)

求:

\[\sum\limits_{i \le x} \text{depth}(\text{lca}(i,y))^k \]

\(\text{lca}(x,y)\) 表示节点 \(x\) 与节点 \(y\) 在有根树上的最近公共祖先。

\(\text{depth}(x)\) 表示节点 \(x\) 的深度,根节点的深度为 \(1\)

由于答案可能很大,你只需要输出答案模 \(998244353\) 的结果。

\(n \le 50000,Q \le 50000,1 \le k \le 10^9\)

Solution

跟上一题很类似,容易想到求深度的次方只需要把之前操作中“路径点加1”改成“对于路径点 \(x\) ,加 \(dep(x)^k-(dep(x)-1)^k\) 即可。

那么树剖之后预处理每个点的系数放进线段树里。

时间复杂度 \(O(nlogn)\)

#include<bits/stdc++.h>
#define pb push_back
#define N 50005
#define mid ((l+r)>>1)
#define mod 998244353
using namespace std;
int n,m,f[N],ans[N],rk[N],tot,top[N],dep[N],son[N],siz[N],K,dfn[N];
int tag[N<<2],sum[N<<2],a[N<<2];
struct query{int num,x;};
vector<query>q[N];
vector<int>t[N];
inline int mo(int x){return x<mod?x:x-mod;}
inline void dfs(int u){
	dep[u]=dep[f[u]]+1,siz[u]=1;
	for(int v:t[u])dfs(v),siz[u]+=siz[v],siz[v]>siz[son[u]]?son[u]=v:0;
}
inline void dfs(int u,int tp){
	rk[u]=++tot,top[u]=tp,dfn[tot]=u;
	if(son[u])dfs(son[u],tp);
	for(int v:t[u])if(v!=son[u])dfs(v,v);
}
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
inline void F(int p,int k){
	sum[p]=(sum[p]+1ll*a[p]*k)%mod;
	tag[p]=mo(tag[p]+k);
}
inline void push_down(int p){
	if(!tag[p])return;
	F(ls(p),tag[p]);
	F(rs(p),tag[p]);
	tag[p]=0;
}
inline void push_up(int p){sum[p]=sum[ls(p)]+sum[rs(p)];}
inline void addsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return F(p,1),void();
	push_down(p);
	if(L<=mid)addsum(ls(p),l,mid,L,R);
	if(R>mid)addsum(rs(p),mid+1,r,L,R);
	push_up(p);
}
inline int getsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return sum[p];
	push_down(p);
	int res=0;
	if(L<=mid)res+=getsum(ls(p),l,mid,L,R);
	if(R>mid)res+=getsum(rs(p),mid+1,r,L,R);
	return res;
}
inline int ksm(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=1ll*res*a%mod;
		a=1ll*a*a%mod,b>>=1;
	}
	return res;
}
inline void build(int p,int l,int r){
	if(l==r)return a[p]=mo(ksm(dep[dfn[l]],K)-ksm(dep[dfn[l]]-1,K)+mod),void();
	build(ls(p),l,mid);
	build(rs(p),mid+1,r);
	a[p]=mo(a[ls(p)]+a[rs(p)]);
}
inline int op(int u){
	int res=0;
	for(;u;u=f[top[u]])res+=getsum(1,1,n,rk[top[u]],rk[u]);
	return res;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>K;
	for(int i=2;i<=n;i++)cin>>f[i],t[f[i]].pb(i);
	dfs(1),dfs(1,1);build(1,1,n);
	for(int i=1,l,x;i<=m;i++)cin>>l>>x,q[l].pb({i,x});
	for(int i=1;i<=n;i++){
		for(int j=i;j;j=f[top[j]])addsum(1,1,n,rk[top[j]],rk[j]);
		for(auto j:q[i])ans[j.num]=op(j.x);
	}
	for(int i=1;i<=m;i++)cout<<ans[i]%mod<<'\n';
}
posted @ 2025-05-31 14:44  linjingxiang  阅读(28)  评论(0)    收藏  举报