P1074 云间飞渡 官方题解
Gioush OJ · P1074 云间飞渡
【部分分:测试点 1∼31\sim 31∼3】
- 原题为 P3572 [POI2014] PTA-Little Bird。
- 对一次询问给出的 ,令 表示到达第 座高台所需的最少疲劳。起点不产生疲劳,所以 。
- 到达 之前的高台 必须满足 。从 跳到 的代价为 ,于是
- 枚举每个转移,单次询问时间复杂度为 。
【部分分:测试点 4∼84\sim 84∼8】
- 转移只会访问长度至多为 的窗口,因此没有必要枚举更早的高台。
- 对第 个状态枚举
即可。一次询问的时间复杂度为 。
- 对全部询问,时间复杂度为
正好对应本档承诺的总工作量。
【部分分:测试点 9∼119\sim 119∼11】
- 观察窗口中的两个候选位置 。若 ,那么即使从 跳到当前高台需要增加 ,也有
- 因此 DP 值更小的候选永远不会比 DP 值更大的候选差。
- 当 时,高度更大的位置更优。因为 越大,条件 越不容易成立,额外代价不会更大。
- 所以窗口中的候选可以按照二元组
排序,最小二元组就是当前转移的最优前驱。
- 使用有序多重集合保存当前窗口内的 。
- 计算 前,删除已经离开窗口的位置 。取集合最小元素对应的位置 ,则
- 再把 插入集合。每个位置插入、删除各一次。
- 单次询问时间复杂度为 ,空间复杂度为 。
【部分分:测试点 12∼1512\sim 1512∼15】
- 对每个 独立运行上一档的有序集合算法。
- 高台高度与所有询问共用,但不同 的窗口边界不同,DP 值也会随之改变,不能把一组询问的集合直接沿用到下一组。
- 每次重新设置 并清空集合。总时间复杂度为 ,空间复杂度为 。
- 满分只差一个问题:如何利用二元组的支配关系,把有序集合降为单调队列。
【正解】
- 设位置 在位置 之前进入队列。若
那么 的 DP 值更小,并且 离开窗口更晚, 永远不可能再成为最优前驱。
-
若 且 ,那么 的高度不小于 ,额外代价也不会更大。同时 存活更久,仍然可以删除 。
-
因此队列从头到尾按照 严格递增,队首始终是窗口内的最优二元组。
-
计算位置 时,先从队首删除所有下标小于 的位置。
-
设当前队首为 ,直接计算
- 插入 前,从队尾不断删除满足
或者
的位置,最后把 放到队尾。
- 每个位置只会进入队列一次,也只会从队首或队尾离开一次,所以所有队列操作的总次数为 。
- 一次询问的时间复杂度为 ,全部询问的时间复杂度为 ,空间复杂度为 。
- 多组数据之间要重新读取高度序列,并在每次询问开始时重置队列与本组使用的 。
- 队列维护的并非单独的高度或 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;}