P1074 · OFFICIAL SOLUTION

P1074 云间飞渡 官方题解

Gioush OJ · P1074 云间飞渡

【部分分:测试点 1∼31\sim 31∼3】

  • 原题为 P3572 [POI2014] PTA-Little Bird
  • 对一次询问给出的 kk,令 fif_i 表示到达第 ii 座高台所需的最少疲劳。起点不产生疲劳,所以 f1=0f_1=0
  • 到达 ii 之前的高台 jj 必须满足 ikj<ii-k\leq j<i。从 jj 跳到 ii 的代价为 [hjhi][h_j\leq h_i],于是
fi=minmax(1,ik)j<i(fj+[hjhi]).f_i=\min_{\max(1,i-k)\leq j<i} \left(f_j+[h_j\leq h_i]\right).
  • 枚举每个转移,单次询问时间复杂度为 O(n2)\mathcal O(n^2)

【部分分:测试点 4∼84\sim 84∼8】

  • 转移只会访问长度至多为 kk 的窗口,因此没有必要枚举更早的高台。
  • 对第 ii 个状态枚举
j=max(1,ik),,i1j=\max(1,i-k),\ldots,i-1

即可。一次询问的时间复杂度为 O(nk)\mathcal O(nk)

  • 对全部询问,时间复杂度为
O(nx=1qkx),\mathcal O\left(n\sum_{x=1}^{q}k_x\right),

正好对应本档承诺的总工作量。

【部分分:测试点 9∼119\sim 119∼11】

  • 观察窗口中的两个候选位置 u,vu,v。若 fu<fvf_u<f_v,那么即使从 uu 跳到当前高台需要增加 11,也有
fu+1fv.f_u+1\leq f_v.
  • 因此 DP 值更小的候选永远不会比 DP 值更大的候选差。
  • fu=fvf_u=f_v 时,高度更大的位置更优。因为 huh_u 越大,条件 huhih_u\leq h_i 越不容易成立,额外代价不会更大。
  • 所以窗口中的候选可以按照二元组
(fj,hj)(f_j,-h_j)

排序,最小二元组就是当前转移的最优前驱。

  • 使用有序多重集合保存当前窗口内的 (fj,hj,j)(f_j,-h_j,j)
  • 计算 fif_i 前,删除已经离开窗口的位置 ik1i-k-1。取集合最小元素对应的位置 pp,则
fi=fp+[hphi].f_i=f_p+[h_p\leq h_i].
  • 再把 (fi,hi,i)(f_i,-h_i,i) 插入集合。每个位置插入、删除各一次。
  • 单次询问时间复杂度为 O(nlogn)\mathcal O(n\log n),空间复杂度为 O(n)\mathcal O(n)

【部分分:测试点 12∼1512\sim 1512∼15】

  • 对每个 kxk_x 独立运行上一档的有序集合算法。
  • 高台高度与所有询问共用,但不同 kxk_x 的窗口边界不同,DP 值也会随之改变,不能把一组询问的集合直接沿用到下一组。
  • 每次重新设置 f1=0f_1=0 并清空集合。总时间复杂度为 O(nqlogn)\mathcal O(nq\log n),空间复杂度为 O(n)\mathcal O(n)
  • 满分只差一个问题:如何利用二元组的支配关系,把有序集合降为单调队列。

【正解】

  • 设位置 uu 在位置 vv 之前进入队列。若
fu>fv,f_u>f_v,

那么 vv 的 DP 值更小,并且 vv 离开窗口更晚,uu 永远不可能再成为最优前驱。

  • fu=fvf_u=f_vhuhvh_u\leq h_v,那么 vv 的高度不小于 uu,额外代价也不会更大。同时 vv 存活更久,仍然可以删除 uu

  • 因此队列从头到尾按照 (fj,hj)(f_j,-h_j) 严格递增,队首始终是窗口内的最优二元组。

  • 计算位置 ii 时,先从队首删除所有下标小于 iki-k 的位置。

  • 设当前队首为 pp,直接计算

fi=fp+[hphi].f_i=f_p+[h_p\leq h_i].
  • 插入 ii 前,从队尾不断删除满足
fback>fif_{\operatorname{back}}>f_i

或者

fback=fi,hbackhif_{\operatorname{back}}=f_i,\qquad h_{\operatorname{back}}\leq h_i

的位置,最后把 ii 放到队尾。

  • 每个位置只会进入队列一次,也只会从队首或队尾离开一次,所以所有队列操作的总次数为 O(n)\mathcal O(n)
  • 一次询问的时间复杂度为 O(n)\mathcal O(n),全部询问的时间复杂度为 O(nq)\mathcal O(nq),空间复杂度为 O(n)\mathcal O(n)
  • 多组数据之间要重新读取高度序列,并在每次询问开始时重置队列与本组使用的 fif_i
  • 队列维护的并非单独的高度或 DP 值,而是由转移式推导出的二元组顺序。

【参考代码】

/* * Author:Ehundategh * Update:2025/7/8 * Title:P3572.cpp * You steal,I kill */#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 1000010using namespace std; struct queue{    int Head=1,Tail=0,Pos[MAXN];    void Init(){Head=1;Tail=0;return;}    bool Empty(){return Head>Tail;}    void Push(int a){Pos[++Tail]=a;return;}    int Pos_Front(){return Pos[Head];}    int Back(){return Pos[Tail];}    void Pop_Front(){Head++;return;}    void Pop_Back(){Tail--;return;}}Q;int T,Dp[MAXN],n,Value[MAXN],q,k;void Solve(){    memset(Dp,0,sizeof(Dp));    scanf("%d",&k);    Q.Init();    Dp[1]=0;Q.Push(1);    for(int i=2;i<=n;i++){        while((!Q.Empty())&&Q.Pos_Front()<i-k) Q.Pop_Front();        if(Value[i]>=Value[Q.Pos_Front()]) Dp[i]++;        Dp[i]+=Dp[Q.Pos_Front()];        while((!Q.Empty())&&Dp[i]<Dp[Q.Back()]) Q.Pop_Back();        while((!Q.Empty())&&Dp[i]==Dp[Q.Back()]&&Value[i]>=Value[Q.Back()]) Q.Pop_Back();        Q.Push(i);    }    printf("%d\n",Dp[n]);}void Tackle(){    scanf("%d",&n);    for(int i=1;i<=n;i++) scanf("%d",&Value[i]);    scanf("%d",&q);    while(q-->0) Solve();}int main(){    int c;    scanf("%d%d",&c,&T);    while(T-->0) Tackle();    return 0;}