6904. 【2020.11.28提高组模拟】T3 树上询问(query)

这篇博客探讨了如何处理树上的询问问题,提出了利用LCA(最近公共祖先)和差分思想来解决从节点L到R的链上的查询。通过将链拆分为L到LCA和LCA到R两部分,结合节点深度信息,可以离线处理询问并使用桶记录数据,从而优化算法的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

你有一棵n 节点的树 T,回答 m个询问,每次询问给你两个整数 l r,问存在多少个整数 k使得从l 沿着 l->r
的简单路径走k 步恰好到达k 。

Input

第一行,两个整数n,m表示节点数和询问数。 之后 n-1行,每行两个整数u,v表示一条边。 之后m 行,每行两个整数 l,r 表示 一个询问,题意同题目描述。

Output

m 行,对于每个询问单独输出一行表示你的答案。

Sample explanation

Data Constraint

Limit

时间限制: 3s

空间限制: 512MB

Solution

考虑将一条 L 到 R 的链拆成 L 到 LCA 和 LCA到 R 。

即 d[ x ]表示 x 节点的深度。

则从 L 到 LCA 上答案就是 d[ L ]-d[ x ]=x 的x 的个数,也就是d[ L ]= d[ x ]+x。

已知d[ L ]为定值,则运用树上差分的思想,可以将 root 到 L 路径上的答案减去 root 到 LCA的答案得到。

那么离线在树上dfs的时候用桶记录一下即可。

 

对于 LCA 到 R 上的答案是 d[ R ]-d[ y ]= len- y 的y的个数,也就是 d[ R ]-len = d[ y ]-y。

同样将询问离线将 根 到 R 的减去 根 到 LCA 的答案即可。

Code 

#include<cstdio> 
#include<cstring>
#include<algorithm>
#include<cmath>
#define I int
#define ll long long
#define F(i,a,b) for(register I i=a;i<=b;i++)
#define Fd(i,a,b) for(register I i=a;i>=b;i--)
#define mem(a,b) memset(a,b,sizeof a)
#define N 300005
using namespace std;
I n,m,x,y,z,l[N],r[N],c[N],d[N],t[N<<1],f[N][21],pre[N],suf[N],tot,len,now,ans[N];
I nx[N<<1],ls[N],v[N<<1],cnt,qt[N<<1],ql[N<<1],qnx[N<<1],qls[N];
I R(I &x){
	x=0;ll w=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') w=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*=w;
}
void add(I x,I y){
	v[++tot]=y,nx[tot]=ls[x],ls[x]=tot;
}
void dg(I x,I y){
	f[x][0]=y;d[x]=d[y]+1;t[d[x]+x]++;pre[x]=t[d[x]];
	for(I k=ls[x];k;k=nx[k]) if(v[k]!=y){
		dg(v[k],x);
	}
	t[d[x]+x]--;
}
I lca(I x,I y){
	if(d[x]<d[y]) swap(x,y);
	Fd(i,19,0) if(d[f[x][i]]>=d[y]) x=f[x][i];
	if(x==y) return x;
	Fd(i,19,0) if(f[x][i]!=f[y][i]){
		x=f[x][i],y=f[y][i];
	}
	return f[x][0];
}
void ins(I x,I len,I id){
	qt[++cnt]=id,qnx[cnt]=qls[x],ql[cnt]=len,qls[x]=cnt;
}
void dfs(I x,I y,I op){
	(op)?t[d[x]+x]++:t[d[x]-x+n]++;
	for(I k=qls[x];k;k=qnx[k]){
		now=(op)?t[ql[k]]:t[d[r[qt[k]>m?qt[k]-m:qt[k]]]-ql[k]+n];
		if(qt[k]>m) ans[qt[k]-m]-=now;else ans[qt[k]]+=now;
	}
	for(I k=ls[x];k;k=nx[k]) if(v[k]!=y) dfs(v[k],x,op);
	(op)?t[d[x]+x]--:t[d[x]-x+n]--;
}
I main(){
	freopen("query.in","r",stdin);
	freopen("query.out","w",stdout);
	R(n),R(m);
	F(i,1,n-1){
		R(x),R(y);
		add(x,y),add(y,x);
	}
	dg(1,0);
	F(j,1,19){
		F(i,1,n) f[i][j]=f[f[i][j-1]][j-1];
	}
	F(i,1,m){
		R(l[i]),R(r[i]);c[i]=lca(l[i],r[i]);
		ans[i]=pre[l[i]];
		ins(r[i],d[l[i]]+d[r[i]]-2*d[c[i]],i);
		ins(c[i],d[l[i]]+d[r[i]]-2*d[c[i]],i+m);
	}
	dfs(1,0,0);
	cnt=0;
	mem(qls,0);
	F(i,1,m) ins(f[c[i]][0],d[l[i]],i+m);
	dfs(1,0,1);
	F(i,1,m) printf("%d\n",ans[i]);
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值