[POI2010] ZAB-Frog
题面翻译
有 n n n 个点,升序给出每个点到起点的距离。有编号为 1 ∼ n 1 \sim n 1∼n 的 n n n 只青蛙分别在第 1 ∼ n 1 \sim n 1∼n 个点上,每次它们会跳到距离自己第 k k k 远的点上。
如果有相同距离的点,就跳到下标更小的点上。
求跳 m m m 次之后,第 i i i 只的青蛙在哪个点上。
输入共一行三个整数:代表 n , k , m n, k, m n,k,m。
输出共一行 n n n 个整数,代表每只青蛙最终所处在的点。
样例 #1
样例输入 #1
5 2 4
1 2 4 7 10
样例输出 #1
1 1 3 1 1
提示
1 ≤ k < n ≤ 1 0 6 1 \le k \lt n \le 10^6 1≤k<n≤106, 1 ≤ m ≤ 1 0 18 1 \le m \le 10^{18} 1≤m≤1018, 1 ≤ p 1 < p 2 < . . . < p n ≤ 1 0 18 1 \le p_1 \lt p_2 \lt ... \lt p_n \le 10^{18} 1≤p1<p2<...<pn≤1018
思路
- 首先看到这个跳点的操作可以想到倍增,想到一开始做的倍增典题的描述的跳跃多数都是直接跳到第 i+k 个点上。所以如果我们可以提前预处理出每一个点会跳到哪里,这道题是板子了。
- 其次我们得知道:倍增都必须得预处理(因为这道题的数据规模嘎嘎的大)。
- 然后,预处理出每个点跳到离自己第k的点的编号,可以用一个类似滑动窗口的单调队列实现。这里不怎么好理解,这里借用以下大佬的图片:
- 因为一个点的第k远是有两个的(一左一右),因此我们的队头在i的左边,队尾在i的右边。
- 总结:对于这种跳跃问题,数据规模比较大的时候,我们就得想到倍增的思路,倍增要预处理,对于什么第k小的数,我们可以用单调队列预处理。
- 本道题的有几个特殊点:1.跳跃次数固定,此时可以用快速幂。2. “区间长度”固定,区间维护第k大的数,此时用单调队列最合适不过了,此时i的第k大数就是L和R。
- 对于单调队列while放在for循环的下面(紧挨着(以前写的都是在if语句下边)),此时我们看的时候得按照上图那样看(要不然很难理解那张图和其他的)。 => 也就是说假定已经求好了i的第k大数, 然后那个while就是判断下一轮区间移动条件,也就是开始准备求i+1的第k大数。
代码
#include<iostream>
#define int long long
using namespace std;
const int N = 1e6+10;
int n,k,m;
int tm[N];
int w[N],ne[N],p[N];
//w题目给的距离原点的距离,ne是第i个点可以跳到哪里(也就是第k远),p是存最终位置的
signed main(){
cin>>n>>k>>m;
for(int i=1;i<=n;i++)cin>>w[i];
int hh=1,tt=k+1;
//对于第一个点,他的第k远肯定是第k+1个点
//tt是新加进来的点
for(int i=1;i<=n;i++){
//这行就是找i+1个点的点k远(因为这行代码是处理执行一遍循环而处理的)
while(tt+1<=n&&w[tt+1]-w[i]<w[i]-w[hh])hh++,tt++;
//如果移动之后当前窗口依旧合法且下一个元素距离当前点的距离小于队首距离当前点的距离则队首不合法,并且下一个元素合法
//答案肯定是队首或者队尾,因为两边才是第k小,中间维护的是前k小。所以判断一下距离大小即可
if(w[tt]-w[i]>w[i]-w[hh])ne[i]=tt;
else ne[i]=hh;
}
for(int i=1;i<=n;i++)p[i]=i;
//由于题目已经说了步数,因此我们直接用快速幂
while(m){
if(m&1){
for(int i=1;i<=n;i++){
p[i]=ne[p[i]];
}
}
for(int i=1;i<=n;i++)tm[i]=ne[i];
for(int i=1;i<=n;i++)ne[i]=tm[tm[i]];//改变ne的值(因为原数组位置有改变)。
m>>=1;
}
for(int i=1;i<=n;i++){
cout<<p[i]<<" ";
}
return 0;
}