P1055 · OFFICIAL SOLUTION

P1055 树的遍历 官方题解

Gioush OJ · P1055 树的遍历

【数据点 1∼31\sim 31∼3】

  • 这一档保证 n3n\leq3,非叶结点最多只有 77 个,可以枚举被唤醒的守卫集合。
  • 固定集合以后,从叶结点向上计算遍历序列。沉睡结点把字典序较小的子树序列放在前面,唤醒结点固定先左后右。
  • 在花费不超过 kk 的集合中取字典序最大的序列即可。

【数据点 4∼64\sim 64∼6】

  • 这一档保证 k=0k=0 且所有花费为正,因此没有守卫能够被唤醒。
  • 每个非叶结点都由小 R 选择先访问哪棵子树。由于叶结点符文互不相同,只需比较两棵子树最终序列的第一个符文。
  • 递归计算两棵子树,再把首项较小的序列放在前面。时间复杂度为 O(n2n)\mathcal{O}(n2^n)

【数据点 7∼197\sim 197∼19】

SuS_u 表示结点 uu 子树中的符文集合,定义

fu,x=使结点 u 的遍历序列首项为 x 的最小花费. f_{u,x}=\text{使结点 $u$ 的遍历序列首项为 $x$ 的最小花费}.
  • uu 是写有符文 quq_u 的叶结点,则 fu,qu=0f_{u,q_u}=0,其余状态均不合法。
  • uu 的左右儿子为 l,rl,r,当 xSlx\in S_l 时,可以唤醒 uu,也可以让右子树首项严格大于 xx
  • xSrx\in S_r 时,不能唤醒 uu,并且左子树首项必须严格大于 xx

【数据点 7∼197\sim 197∼19:状态转移】

完整转移为

fu,x={fl,x+min(wu,minySr,y>xfr,y),xSl,fr,x+minySl,y>xfl,y,xSr. f_{u,x}= \begin{cases} f_{l,x}+\min\left(w_u,\displaystyle\min_{y\in S_r,\,y>x}f_{r,y}\right),&x\in S_l,\\[8pt] f_{r,x}+\displaystyle\min_{y\in S_l,\,y>x}f_{l,y},&x\in S_r. \end{cases}
  • 空集合的最小值视为正无穷。加法中只要有一项不合法,结果也不合法。
  • 直接枚举 xx 与另一棵子树的 yy,可以做到 O(4n)\mathcal{O}(4^n),足以通过 n8n\leq8

【正解】

定义严格后缀最小值

gu,x=minySu,y>xfu,y. g_{u,x}=\min_{y\in S_u,\,y>x}f_{u,y}.

于是转移可以写成

fu,x={fl,x+min(wu,gr,x),xSl,fr,x+gl,x,xSr. f_{u,x}= \begin{cases} f_{l,x}+\min(w_u,g_{r,x}),&x\in S_l,\\ f_{r,x}+g_{l,x},&x\in S_r. \end{cases}
  • 将两棵子树的状态按 xx 合并,并从右向左维护后缀最小值,每个状态只需常数次计算。
  • 整棵树共有 O(n2n)\mathcal{O}(n2^n) 个状态,建表时间与空间复杂度均为 O(n2n)\mathcal{O}(n2^n)

【答案恢复】

  • 在根结点中选择最大的 xx,使得 f1,xkf_{1,x}\leq k,这就是最终序列的首项。
  • 恢复答案时,先预留保证当前子树先被访问所需的最小花费,再把剩余预算交给先访问的子树,使这一段序列尽可能大。
  • 第一段确定以后,再在仍满足首项比较关系的状态中选择第二棵子树的最大首项,继续递归。
  • 每一步都先最大化当前尚未确定的第一个位置,因此得到的完整序列字典序最大。

【实现与复杂度】

  • 当当前首项 xx 来自左子树时,需要预留 min(wu,gr,x)\min(w_u,g_{r,x})。之后右子树首项若小于 xx,就必须额外支付 wuw_u 唤醒当前守卫。
  • xx 来自右子树时,需要预留 gl,xg_{l,x},并且最后选择的左子树首项必须严格大于 xx
  • 实现中所有花费以及预算均使用 long long,不合法状态用足够大的正数表示。

【参考代码】

/*Author:EhundateghDate:2026/7/24Name:traverse.cppYou steal,I kill.*/#include <cstdio>#include <vector>#include <algorithm>#define MAXN 100010using namespace std;const long long INF=(1ll<<62); int c,n,Leaf,T,Line[MAXN<<1],Pos[MAXN],Right[MAXN<<1];long long k,Val[MAXN]; struct Data {    int Mark;    long long Val;}; vector <Data> Dp[MAXN<<1];vector <long long> Suff[MAXN<<1]; long long Add(long long x,long long y){return (x>=INF||y>=INF)?INF:x+y;} long long Query(int Now,int x) {    int l=0,r=(int)Dp[Now].size();    while (l<r) {        int Mid=(l+r)>>1;        if (Dp[Now][Mid].Mark<=x) l=Mid+1;        else r=Mid;    }    return l==(int)Dp[Now].size()?INF:Suff[Now][l];} void Build(int Now) {    if (Now>=Leaf) {        Right[Now]=Now-Leaf+1;        Dp[Now].push_back({Line[Now],0});        Suff[Now].push_back(0);        return;    }    int ls=Now<<1,rs=Now<<1|1;    Build(ls);Build(rs);    Right[Now]=Right[rs];    int i=0,j=0,p=0,q=0;    Dp[Now].reserve(Dp[ls].size()+Dp[rs].size());    while (i<(int)Dp[ls].size()||j<(int)Dp[rs].size()) {        bool Tag=j==(int)Dp[rs].size()||            (i<(int)Dp[ls].size()&&Dp[ls][i].Mark<Dp[rs][j].Mark);        int Mark=Tag?Dp[ls][i].Mark:Dp[rs][j].Mark;        while (p<(int)Dp[ls].size()&&Dp[ls][p].Mark<=Mark) p++;        while (q<(int)Dp[rs].size()&&Dp[rs][q].Mark<=Mark) q++;        if (Tag) {            long long Ret=q==(int)Dp[rs].size()?INF:Suff[rs][q];            Dp[Now].push_back({Mark,Add(Dp[ls][i].Val,min(Val[Now],Ret))});            i++;        }        else {            long long Ret=p==(int)Dp[ls].size()?INF:Suff[ls][p];            Dp[Now].push_back({Mark,Add(Dp[rs][j].Val,Ret)});            j++;        }    }    Suff[Now].resize(Dp[Now].size());    for (int t=(int)Dp[Now].size()-1;t>=0;t--) {        Suff[Now][t]=Dp[Now][t].Val;        if (t+1<(int)Dp[Now].size()) Suff[Now][t]=min(Suff[Now][t],Suff[Now][t+1]);    }    return;} long long Print(int Now,int x,long long Limit) {    if (Now>=Leaf) {printf("%d ",Line[Now]);return 0;}    int ls=Now<<1,rs=Now<<1|1,Temp=0;    if (Pos[x]<=Right[ls]) {        long long Need=min(Val[Now],Query(rs,x));        long long Ret=Print(ls,x,Limit-Need);        Limit-=Ret;        for (int i=(int)Dp[rs].size()-1;i>=0;i--) {            long long Extra=Dp[rs][i].Mark<x?Val[Now]:0;            if (Add(Dp[rs][i].Val,Extra)<=Limit) {                Temp=Dp[rs][i].Mark;                break;            }        }        long long Extra=Temp<x?Val[Now]:0;        return Ret+Extra+Print(rs,Temp,Limit-Extra);    }    long long Need=Query(ls,x);    long long Ret=Print(rs,x,Limit-Need);    Limit-=Ret;    for (int i=(int)Dp[ls].size()-1;i>=0;i--) {        if (Dp[ls][i].Mark>x&&Dp[ls][i].Val<=Limit) {            Temp=Dp[ls][i].Mark;            break;        }    }    return Ret+Print(ls,Temp,Limit);} void Solve() {    scanf("%d%lld",&n,&k);Leaf=1<<n;    for (int i=1;i<(Leaf<<1);i++) Dp[i].clear(),Suff[i].clear();    for (int i=1;i<Leaf;i++) scanf("%lld",&Val[i]);    for (int i=Leaf;i<(Leaf<<1);i++) {        scanf("%d",&Line[i]);        Pos[Line[i]]=i-Leaf+1;    }    Build(1);    int Temp=0;    for (int i=(int)Dp[1].size()-1;i>=0;i--) {        if (Dp[1][i].Val<=k) {            Temp=Dp[1][i].Mark;            break;        }    }    Print(1,Temp,k);    printf("\n");    return;} int main() {    scanf("%d%d",&c,&T);    while (T-->0) Solve();    return 0;}