P1056 · OFFICIAL SOLUTION

P1056 树上查询 官方题解

Gioush OJ · P1056 树上查询

【数据点 1∼21\sim 21∼2】

  • 这一档保证 n,q300n,q\leq300。预处理任意连续区间的权值和与最近公共祖先深度。
  • 对每次查询枚举 labrl\leq a\leq b\leq r,检查区间权值和是否达到 kk,并更新最大深度。
  • 时间复杂度为 O(n2+qn2)\mathcal{O}(n^2+qn^2),空间复杂度为 O(n2)\mathcal{O}(n^2)

【数据点 3∼53\sim 53∼5】

  • 考虑二分答案深度 dd。深度至少为 dd 的最近公共祖先,说明整个区间位于某个深度为 dd 的结点子树中。
  • 枚举深度为 dd 的结点,计算它的子树编号区间与 [l,r][l,r] 的交集权值和,判断是否至少为 kk
  • 一次判定需要 O(n)\mathcal{O}(n),每次查询需要 O(nlogn)\mathcal{O}(n\log n),足以通过 n,q2×103n,q\leq2\times10^3

【数据点 6∼96\sim 96∼9】

  • 这一档的树是一条链,所以结点 ii 的深度就是 ii,区间 [a,b][a,b] 的最近公共祖先就是结点 aa
  • 对一次查询,固定左端点 aa 后,最大的可用权值和为 w(a,r)w(a,r)。我们需要找到满足 w(a,r)kw(a,r)\geq k 的最大 aa
  • 权值均为正,前缀和严格递增,可以二分这个位置。每次查询时间复杂度为 O(logn)\mathcal{O}(\log n)

【数据点 10∼1310\sim 1310∼13】

  • 这一档保证 k=w(l,r)k=w(l,r)。由于权值均为正,只有完整区间 [l,r][l,r] 的权值和能够达到 kk
  • 因而答案就是结点 l,l+1,,rl,l+1,\ldots,r 的最近公共祖先深度。
  • 在深度优先遍历编号下,这个最近公共祖先等于 lca(l,r)\operatorname{lca}(l,r),使用倍增即可回答。

【数据点 14∼2014\sim 2014∼20】

  • 这一档保证 kmini=lrwik\leq\min_{i=l}^{r}w_i,所以任意单点区间都满足权值条件。
  • 单点区间 [i,i][i,i] 的最近公共祖先就是结点 ii。答案转化为 [l,r][l,r] 中结点深度的最大值。
  • 对深度数组建立稀疏表或线段树即可。预处理 O(nlogn)\mathcal{O}(n\log n),每次查询 O(1)\mathcal{O}(1)O(logn)\mathcal{O}(\log n)

【正解】

固定深度 dd,考虑深度为 dd 的一个结点 uu,其子树编号区间记为 IuI_u

  • 若一个合法区间的最近公共祖先深度至少为 dd,那么该区间一定完整位于某个 IuI_u 中。
  • 反过来,Iu[l,r]I_u\cap[l,r] 仍然是连续区间。若这个交集的权值和至少为 kk,直接选择其中一段达到 kk 的前缀即可。
  • 因此深度 dd 可行,当且仅当存在一个深度为 dd 的结点,使其子树与询问区间的交集权值和至少为 kk

【数据结构实现】

  • 同一深度的子树区间互不相交,并且按照左端点与右端点同时递增的顺序排列。
  • [l,r][l,r] 相交的子树构成一个连续段。第一棵与最后一棵子树可能只被截取一部分,中间子树一定完整包含在 [l,r][l,r] 中。
  • 使用权值前缀和计算两个边界交集。对每个深度的完整子树权值建立稀疏表,中间部分直接查询最大值。
  • 两次二分找到相交子树段,一次判定的时间复杂度为 O(logn)\mathcal{O}(\log n)

【实现与复杂度】

  • 若深度 dd 可行,那么所有小于 dd 的深度也可行。因此可以二分最大的可行深度。
  • 预处理所有子树右端点、结点深度、权值前缀和,以及每个深度的稀疏表。
  • 预处理时间与空间复杂度为 O(nlogn)\mathcal{O}(n\log n),每次查询时间复杂度为 O(log2n)\mathcal{O}(\log^2 n)
  • 权值前缀和、子树权值与询问阈值需要使用 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;}