题解:P17139 [KOI 2026 #1] 跳跃

· · 题解

题意简述

i 个踏板位于 (X_i,i)。只能跳向编号更大的踏板,且一次跳跃要求横坐标差的绝对值不超过 D。求从每个踏板出发能够到达多少个踏板,包括起点本身。

解题思路

由于编号只能增大,所有可达关系都指向后缀。倒序处理踏板时,后继踏板的答案已经求出。

对起点 i,按照横坐标与 X_i 的关系,把可达踏板分为三个互不相交的集合:

先研究 R_i。若 R_i 非空,令 k 为其中编号最小的踏板。考虑一条从 ik 的路径。因为 k 是路径上第一个横坐标大于 X_i 的踏板,其前驱 u 满足 X_u\le X_i。由 u 能跳到 k 可得:

X_k-X_i\le X_k-X_u\le D

所以 i 可以直接跳到 k

此时 R_i 可以精确拆分为:

R_i=R_k\mathbin{\cup}\{j\mid i<j,X_i<X_j\le X_k\}

右侧第二部分中的踏板都能由 i 直接到达。对于 R_k 中的踏板,可以先从 ik,再沿原路径到达。故右侧包含于 R_i

反过来,任取 j\in R_i。若 X_j\le X_k,它属于第二部分。若 X_j>X_k,在一条从 ij 的路径上,取第一个横坐标大于 X_k 的踏板 v,并记其前驱为 u。此时 X_u\le X_k<X_v,所以:

X_v-X_k\le X_v-X_u\le D 对左侧完全对称。若 $k$ 是 $L_i$ 中编号最小的踏板,则: $$ L_i=L_k\mathbin{\cup}\{j\mid i<j,X_k\le X_j<X_i\} $$ 横坐标等于 $X_i$ 的任意后续踏板都能由 $i$ 一步到达。因此,$E_i$ 恰好由 $i$ 与后缀中所有坐标等于 $X_i$ 的踏板组成。 倒序枚举 $i$,并维护已经加入的后缀。为了使用上述递推,需要支持两种区间查询: 1. 查询某个横坐标区间内的最小踏板编号,用于找到 $L_i$ 或 $R_i$ 中的 $k$; 2. 查询区间内已经加入的踏板数量,用于统计递推中新增加且能够直接跳到的部分。 先将全部横坐标离散化。用迭代线段树维护每个坐标区间中的最小编号,用树状数组维护数量前缀和。处理 $i$ 时,后缀 $i+1,\dots,N$ 已全部加入。 对右侧,在线段树中查询坐标区间 $(X_i,X_i+D]$ 的最小编号 $k$。若存在,就继承 $|R_k|$,再用树状数组统计 $(X_i,X_k]$ 内的后缀踏板数量。左侧使用区间 $[X_i-D,X_i)$ 寻找 $k$,并统计 $[X_k,X_i)$。等坐标部分可以直接查询坐标 $X_i$ 的已有数量并加一。 每个踏板只进行常数次区间查询和一次插入。时间复杂度为 $O(N\log N)$,空间复杂度为 $O(N)$。 ## 正确性证明 对每个 $i$,上述论证证明了 $R_i$ 与 $L_i$ 的递推等式。两个等式中的继承集合和直接跳跃集合横坐标范围不交,因此计数不会重复。$E_i$ 中的踏板与 $i$ 横坐标相同,且编号更大时一定能一步到达;可达路径又不能返回更小编号,所以后缀等坐标计数恰好等于 $|E_i|$。 倒序处理保证递推依赖的 $|L_k|$ 与 $|R_k|$ 已经求出。线段树返回指定范围内编号最小的后缀踏板,正好对应递推定义的 $k$;树状数组则准确统计直接跳跃集合的大小。因此算法分别得到三个集合的真实大小。 三个集合按照横坐标与 $X_i$ 的大小关系划分全部可达踏板,彼此不交且没有遗漏。将它们的大小相加,所得就是每个起点的可达踏板总数。 ## 参考代码 ```cpp #include <bits/stdc++.h> using namespace std; const int N=300005; const int M=1048576; const int inf=0x3f3f3f3f; int n,d,m,s; int x[N],v[N],bit[N],tr[M]; int lt[N],rt[N],ans[N]; void add(int x,int val) { for(int i=x;i<=m;i+=i&-i)bit[i]++; x+=s-1; tr[x]=val; while(x>1) { x>>=1; tr[x]=min(tr[x<<1],tr[x<<1|1]); } } int ask_sum(int x) { int res=0; for(int i=x;i;i-=i&-i)res+=bit[i]; return res; } int ask_min(int l,int r) { l+=s-1; r+=s-1; int res=inf; while(l<=r) { if(l&1)res=min(res,tr[l++]); if(!(r&1))res=min(res,tr[r--]); l>>=1; r>>=1; } return res; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>d; for(int i=1;i<=n;i++) { cin>>x[i]; v[i]=x[i]; } sort(v+1,v+n+1); m=unique(v+1,v+n+1)-v-1; s=1; while(s<m)s<<=1; fill(tr,tr+s*2,inf); for(int i=n;i>=1;i--) { int p=lower_bound(v+1,v+m+1,x[i])-v; ans[i]=ask_sum(p)-ask_sum(p-1)+1; int l=upper_bound(v+1,v+m+1,x[i])-v; int r=upper_bound(v+1,v+m+1,x[i]+d)-v-1; if(l<=r) { int k=ask_min(l,r); if(k!=inf) { int q=lower_bound(v+1,v+m+1,x[k])-v; rt[i]=rt[k]+ask_sum(q)-ask_sum(l-1); ans[i]+=rt[i]; } } l=lower_bound(v+1,v+m+1,x[i]-d)-v; r=lower_bound(v+1,v+m+1,x[i])-v-1; if(l<=r) { int k=ask_min(l,r); if(k!=inf) { int q=lower_bound(v+1,v+m+1,x[k])-v; lt[i]=lt[k]+ask_sum(r)-ask_sum(q-1); ans[i]+=lt[i]; } } add(p,i); } for(int i=1;i<=n;i++)cout<<ans[i]<<(i==n?'\n':' '); return 0; } ```