洛谷2509 [POI2010]ZAB-Frog 题解(倍增,单调队列,思维,好题)

原题链接:luogu

题意简述:

(蒯来的)

数轴上有n个点,有一个青蛙在这些点上跳;规则是每次向距当前点第k小的点跳,如果有相同距离则向下标较小的跳;求从每个点出发跳了m次后在哪里;

思路

这个是我集训某天的原题。像这种第 k k k大的问题通常是最难处理的问题。但是我们非常明显的有一个暴力想法:由于 k k k是定值,预处理出每个点开始第 k k k大到哪,然后暴力跳一下即珂。 O ( n 2 + n m ) O(n^2+nm) O(n2+nm)???

珂是我真的就是一个智障 w o c woc woc,都想到这里了,再结合昨天讲倍增,不好好想想倍增做法! f ∗ ∗ k f**k fk!(我太菜了。。。)
倍增做法:设 j u m p [ i ] [ k ] jump[i][k] jump[i][k]表示从 i i i开始跳 2 k 2^k 2k步能到哪里,然后你就只要 j u m p [ i ] [ k ] = j u m p [ j u m p [ i ] [ k − 1 ] ] [ k − 1 ] jump[i][k]=jump[jump[i][k-1]][k-1] jump[i][k]=jump[jump[i][k1]][k1]转移,然后二进制拆分 m m m跳一下即珂。

但是我们还面临一个问题:求第 k k k大这件事,是 O ( n ) O(n) O(n)的( S T L STL STL函数 n t h _ e l e m e n t nth\_element nth_element珂以 O ( n ) O(n) O(n)做这个东西,而不是排序的 n l o g n nlogn nlogn)。一次 O ( n ) O(n) O(n),一共就 O ( n 2 ) O(n^2) O(n2),稳 T T T

观察标签,我们猜这个东西用单调队列优化。其实不用单调队列,用双指针即珂,只不过用了单调队列的思想。设 l , r l,r l,r表示点 i i i到左边,右边的第 k k k大。初始值 l = 1 , r = k + 1 l=1,r=k+1 l=1,r=k+1。然后当我们的中心点 i i i增加时,左边的所有距离都增加了,所以 l l l会增加。右边的所有距离都减少了,同时, r r r也是会增加的。然后我们就不断的让 l , r l,r l,r增加,直到满足条件为止(注意 l < = i , r < = n l<=i,r<=n l<=i,r<=n)由于单调性, l , r l,r l,r虽然一次珂能动很多次,但是加起来肯定是 < = n <=n <=n的。这样就保证了时间复杂度。

代码:

#include
using namespace std;
namespace Flandle_Scarlet
{
    #define N 1001000
    #define ll long long
    ll n,k,m;
    ll p[N];
    void Input()
    {
        scanf("%lld%lld%lld",&n,&k,&m);
        for(int i=1;i<=n;++i)
        {
            scanf("%lld",&p[i]);
        }
    }

    int jump[N][64];
    void Soviet()
    {
        int l=1,r=k+1;
        jump[1][0]=k+1;//处理出jump[1][0](非常明显就是k+1)
        for(int i=2;i<=n;++i)
        {
            while(r<i) ++l,++r;
            while(l<i and r<n)//说好的单调队列,实际上只要双指针模拟一下即珂
            //然后队列。。。就神奇的没了。。。
            {
                if (p[r+1]-p[i]<p[i]-p[l])//不满足条件
                {
                    ++l,++r;
                }
                else break;
            }
            if (p[i]-p[l]>=p[r]-p[i]) jump[i][0]=l;//如果相同的时候也是取左边的,所以注意这里是严格的<=,不能换
            else jump[i][0]=r;
        }
        for(int i=1;i<=62;++i)
        {
            for(int j=1;j<=n;++j)
            {
                jump[j][i]=jump[jump[j][i-1]][i-1];
                //转移
            }
        }
        for(int i=1;i<=n;++i)
        {
            int u=i;
            for(int j=0;j<=62;++j)
            {
                if ((m>>j)&1)//数位拆分+倍增
                {
                    u=jump[u][j];
                }
            }
            printf("%d ",u);
        }putchar('\n');
    }
    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Input();
        Soviet();
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

你可能感兴趣的:(洛谷)