BZOJ2093[Poi2010] Frog

来源:互联网 发布:打淘宝客服要话费吗 编辑:程序博客网 时间:2024/05/01 19:39

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次是一个重复的过程,因此对于这种问题我们就可以用倍增优化到log级别。顺便说一句,这题还卡空间,所以倍增数组只开两个。

既然想到用倍增求解后,问题就只剩下一个内容了:如何求离一个点第k近的点。

最开始想的就是二分,二分一个左端点,直接检验右端点是否可以,但是这样写起来效果并不好,代码很恶心

#include<stdio.h>#include<ctype.h>#include<iostream>#define M 1000005#define ll long longusing 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);    }
0 0
原创粉丝点击