P1056 树上查询 官方题解
Gioush OJ · P1056 树上查询
【数据点 1∼21\sim 21∼2】
- 这一档保证 。预处理任意连续区间的权值和与最近公共祖先深度。
- 对每次查询枚举 ,检查区间权值和是否达到 ,并更新最大深度。
- 时间复杂度为 ,空间复杂度为 。
【数据点 3∼53\sim 53∼5】
- 考虑二分答案深度 。深度至少为 的最近公共祖先,说明整个区间位于某个深度为 的结点子树中。
- 枚举深度为 的结点,计算它的子树编号区间与 的交集权值和,判断是否至少为 。
- 一次判定需要 ,每次查询需要 ,足以通过 。
【数据点 6∼96\sim 96∼9】
- 这一档的树是一条链,所以结点 的深度就是 ,区间 的最近公共祖先就是结点 。
- 对一次查询,固定左端点 后,最大的可用权值和为 。我们需要找到满足 的最大 。
- 权值均为正,前缀和严格递增,可以二分这个位置。每次查询时间复杂度为 。
【数据点 10∼1310\sim 1310∼13】
- 这一档保证 。由于权值均为正,只有完整区间 的权值和能够达到 。
- 因而答案就是结点 的最近公共祖先深度。
- 在深度优先遍历编号下,这个最近公共祖先等于 ,使用倍增即可回答。
【数据点 14∼2014\sim 2014∼20】
- 这一档保证 ,所以任意单点区间都满足权值条件。
- 单点区间 的最近公共祖先就是结点 。答案转化为 中结点深度的最大值。
- 对深度数组建立稀疏表或线段树即可。预处理 ,每次查询 或 。
【正解】
固定深度 ,考虑深度为 的一个结点 ,其子树编号区间记为 。
- 若一个合法区间的最近公共祖先深度至少为 ,那么该区间一定完整位于某个 中。
- 反过来, 仍然是连续区间。若这个交集的权值和至少为 ,直接选择其中一段达到 的前缀即可。
- 因此深度 可行,当且仅当存在一个深度为 的结点,使其子树与询问区间的交集权值和至少为 。
【数据结构实现】
- 同一深度的子树区间互不相交,并且按照左端点与右端点同时递增的顺序排列。
- 与 相交的子树构成一个连续段。第一棵与最后一棵子树可能只被截取一部分,中间子树一定完整包含在 中。
- 使用权值前缀和计算两个边界交集。对每个深度的完整子树权值建立稀疏表,中间部分直接查询最大值。
- 两次二分找到相交子树段,一次判定的时间复杂度为 。
【实现与复杂度】
- 若深度 可行,那么所有小于 的深度也可行。因此可以二分最大的可行深度。
- 预处理所有子树右端点、结点深度、权值前缀和,以及每个深度的稀疏表。
- 预处理时间与空间复杂度为 ,每次查询时间复杂度为 。
- 权值前缀和、子树权值与询问阈值需要使用
long long。
【参考代码】
/*Author:EhundateghDate:2026/8/2Name:query.cppYou steal,I kill.*/#include <cstdio>#include <vector>#include <algorithm>#define MAXN 200010using namespace std;int c,T,n,q,MaxDepth,LastDepth,Father[MAXN],Depth[MAXN],Right[MAXN];long long Line[MAXN],Pre[MAXN];struct Data { vector <int> St,Ed,Log; vector <long long> Sum; vector <vector <long long> > ST; void Clear(){St.clear();Ed.clear();Log.clear();Sum.clear();ST.clear();} void Build(){ int Len=St.size(); Sum.resize(Len);Log.assign(Len+1,0); for(int i=0;i<Len;i++) Sum[i]=Pre[Ed[i]]-Pre[St[i]-1]; for(int i=2;i<=Len;i++) Log[i]=Log[i>>1]+1; if(!Len) return; ST.assign(Log[Len]+1,vector <long long>(Len));ST[0]=Sum; for(int j=1;j<(int)ST.size();j++){ for(int i=0;i+(1<<j)<=Len;i++){ ST[j][i]=max(ST[j-1][i],ST[j-1][i+(1<<(j-1))]); } } return; } long long Query(int l,int r){ if(l>r) return -1; int p=Log[r-l+1]; return max(ST[p][l],ST[p][r-(1<<p)+1]); }}Level[MAXN];bool Check(int d,int l,int r,long long k){ Data &Now=Level[d]; int x=lower_bound(Now.Ed.begin(),Now.Ed.end(),l)-Now.Ed.begin(); int y=upper_bound(Now.St.begin(),Now.St.end(),r)-Now.St.begin()-1; if(x>y) return false; int Left=max(l,Now.St[x]),RightPos=min(r,Now.Ed[x]); long long Ret=Left<=RightPos?Pre[RightPos]-Pre[Left-1]:0; if(x!=y){ Left=max(l,Now.St[y]);RightPos=min(r,Now.Ed[y]); if(Left<=RightPos) Ret=max(Ret,Pre[RightPos]-Pre[Left-1]); } Ret=max(Ret,Now.Query(x+1,y-1)); return Ret>=k;}int Calc(int l,int r,long long k){ int Left=1,RightPos=MaxDepth; while(Left<RightPos){ int Mid=(Left+RightPos+1)>>1; if(Check(Mid,l,r,k)) Left=Mid; else RightPos=Mid-1; } return Left;}void Solve(){ for(int i=1;i<=LastDepth;i++) Level[i].Clear(); scanf("%d%d",&n,&q); Father[1]=0;Depth[1]=1;MaxDepth=1; for(int i=2;i<=n;i++) scanf("%d",&Father[i]); Pre[0]=0; for(int i=1;i<=n;i++){ scanf("%lld",&Line[i]); Pre[i]=Pre[i-1]+Line[i]; Right[i]=i; if(i>1) Depth[i]=Depth[Father[i]]+1; MaxDepth=max(MaxDepth,Depth[i]); } for(int i=n;i>=2;i--) Right[Father[i]]=max(Right[Father[i]],Right[i]); for(int i=1;i<=n;i++){ Level[Depth[i]].St.push_back(i); Level[Depth[i]].Ed.push_back(Right[i]); } for(int i=1;i<=MaxDepth;i++) Level[i].Build(); LastDepth=MaxDepth; while(q-->0){ int l,r;long long k; scanf("%d%d%lld",&l,&r,&k); printf("%d\n",Calc(l,r,k)); } return;}int main(){ scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}