BZOJ2093[Poi2010] Frog
Description
一个条河无限宽,上面有n块石头,石头离左边的河岸(无限宽,右边河岸不晓得在哪)距离严格递增,现在Zxl想锻炼自己的跳跃能力(谁叫他在班里外号是鸟怪。。畸形),他在某一块石头上,想跳到离他这块石头第k远的石头上去,假如离他第k远的石头不是唯一的,他就选离岸最近的那一个(不然回不去了),他想你让他知道,从每块石头开始跳了m次后,自己在哪。
Input
第一行有3个由空格隔开的整数n, k (n, k <= 1,000,000), m (m <= 10^18)。
第二行有n个正整数,第i个数表示第i块石头离左岸的距离,保证输入的n个正整数严格递增,并且不超过10^18。
Output
一行n个由空格隔开的整数,第i个表示Zxl从第i块石头开始跳,跳m次后会在哪个石头上。
Sample Input
5 2 4
1 2 4 7 10
Sample Output
1 1 3 1 1
Solution:
自己对倍增的想法掌握的还不是很扎实啊…
首先,我们为什么要想到倍增:跳跃
m
次是一个重复的过程,因此对于这种问题我们就可以用倍增优化到
既然想到用倍增求解后,问题就只剩下一个内容了:如何求离一个点第k近的点。
最开始想的就是二分,二分一个左端点,直接检验右端点是否可以,但是这样写起来效果并不好,代码很恶心。
#include<stdio.h>
#include<ctype.h>
#include<iostream>
#define M 1000005
#define ll long long
using namespace std;
int d[2][M],ans[M];
ll A[M];
int min(int a,int b){return a<b?a:b;}
inline void Rd(ll &res){
char c;res=0;
while(c=getchar(),!isdigit(c));
do{
res=(res<<1)+(res<<3)+(c^48);
}while(c=getchar(),isdigit(c));
}
int main(){
int n,k,sz=0;ll m;
scanf("%d %d",&n,&k);cin>>m;
for(int i=1;i<=n;i++)Rd(A[i]);
for(int i=1;i<=n;i++){
int L=0,R=min(i-1,k),res;
while(L<=R){
int mid=(L+R)>>1;
if(i+k-mid>n){
L=mid+1;
continue;
}
if(i+k-mid<n&&A[i+k-mid+1]-A[i]<A[i]-A[i-mid]){
R=mid-1;
continue;
}
if(i-mid>1&&A[i]-A[i-mid-1]<=A[i+k-mid]-A[i]){
L=mid+1;
continue;
}
res=mid;
break;
}
d[0][i]=(A[i]-A[i-res]>=A[i+k-res]-A[i]?i-res:i+k-res);
}
int cur=0;
for(int i=1;i<=n;i++)ans[i]=i;
while(m){
if(m&1){
for(int i=1;i<=n;i++)
ans[i]=d[cur][ans[i]];
}
for(int i=1;i<=n;i++)
d[!cur][i]=d[cur][d[cur][i]];
m>>=1,cur=!cur;
}
for(int i=1;i<=n;i++)
printf("%d%c",ans[i],i==n?'\n':' ');
return 0;
}
然后大犇给出了 O(n) 求第k远点的方法,简直虐哭…
我们要求的内容是关于一个点的左边的一个端点与右边的一个端点(并且这两个端点之间的距离是确定的),于是我们就在单调性上研究研究。可以发现的是,这两个端点只会向右延伸,于是直接向右边一直移动指针过去就可以了。
int L=1,R=k+1;
d[0][1]=k+1;
for(int i=2;i<=n;i++){
while(R<n&&L<i&&A[R+1]-A[i]<A[i]-A[L])R++,L++;
d[0][i]=(A[i]-A[L]>=A[R]-A[i]?L:R);
}