NOIP2012 开车旅行 【倍增】

7 篇文章 0 订阅
70分算法

暴力预处理出对于每一个点他右边最近、次近的点的编号,对于每一个询问,暴力模拟开车过程即可。

100算法

和上面一样我们得预处理出每一个点最近、次近的点得编号,但我们不可以使用O( n2 )的算法。

预处理方法一:线段树。线段树维护三个值,区间内最小值、最大值、数的个数。从右往左找(n~1),当找到第i个点时,第i+1个点到第n个点的高度值已经更新过了线段树。通过线段树找出高度比Hi大的最小、次小值,以及找出高度比Hi小的最大、次大值,便可以更新第i个点的最近、次近的点的编号了。因为Hi的值可能很大,会空间超限,所以做之前需要离散化一次。

预处理方法二:双向链表。相比线段树,不需要离散化,但是细节多,调试较麻烦。先将Hi从小到大排序,将两两相邻的两个点连起来。再从前往后枚举i,对于每一个点i,找出它后面连着的两个点(即为大于Hi的最小、次小点)和找出它前面连着的两个点(即为小于Hi的最大、次大点),这样就可以更新出第i个点的最近、次近点。之后,再将第i的点从链表中删去,将其前后的两个元素相连起来。

预处理方法三:set。详见代码。

预处理完毕后,就可以开始求答案了。
如果A从x点开车至y点,然后B从y点开车到z点,则我们视为x向z来了一条边,我们称从x开车到z成为“一轮”。
接下来我们要求出在点i开车经过了 2j 轮后到达的点的编号,设其为p[i][j]。首先,从后往前枚举i,我们可以通过预处理出来的数据求出m[i][0],之后对于每一个p[i][j]我们可以从p[i][j-1]转移过来。(设k=p[i][j-1]),则p[i][j]=p[k][j-1],即等于i跳了两次 2j1 轮,即 2j 轮,跳到不能跳为止。同时处理一下A开过的路程和B开过的路程。

上面做的一切都是为了接下来的答案求值。接下来要介绍一种算法——倍增。倍增,顾名思义,就是成倍成倍的增长。第一次先开 217 轮(最多),第二次再开 216 轮,然后 215 轮、 214 轮……可以跳 2j 轮便跳(即不会越界),不行就不跳。此时开车开到再也开不了为止,这个点便是终点。这样就可以在很短的时间内求出答案。

注:答案会很大,需要开int64(long long)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<set>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=100005;
struct node
{
    int pos,h;
    inline bool operator < (const node &b)const
    {return h<b.h;}
}a[N];
struct node1
{
    int pos,dis;
    inline bool operator < (const node1 &b)const
    {
        if(dis==b.dis)return a[pos].h<a[b.pos].h;
        return dis<b.dis;
    }
}t[5];
int n,m;
int p[N][20],nxta[N],nxtb[N];
ll disa[N][20],disb[N][20];
set<node>S;

void Find(int x)
{
    int cnt=0;
    set<node>::iterator it=S.find(a[x]);
    if(it!=S.begin())
    {
        --it;
        t[++cnt]=(node1){it->pos,a[x].h-it->h};
        if(it!=S.begin())
        {
            --it;
            t[++cnt]=(node1){it->pos,a[x].h-it->h};
            ++it;
        }
        ++it;
    }
    if((++it)!=S.end())
    {
        t[++cnt]=(node1){it->pos,it->h-a[x].h};
        if((++it)!=S.end())
            t[++cnt]=(node1){it->pos,it->h-a[x].h};
        --it;
    }
    --it;
    sort(t+1,t+cnt+1);
    nxtb[x]=t[1].pos;
    if(cnt>1)nxta[x]=t[2].pos;
}

void Init()
{
    for(int i=1;i<=n;i++)
    {
        int na=nxta[i],nb=nxtb[na];
        disa[i][0]=na?abs(a[na].h-a[i].h):0;
        disb[i][0]=nb?abs(a[nb].h-a[na].h):0;
        p[i][0]=nb;
    }
    for(int j=1;j<20;j++)
        for(int i=1;i<=n;i++)
        {
            disa[i][j]=disa[i][j-1]+disa[p[i][j-1]][j-1];
            disb[i][j]=disb[i][j-1]+disb[p[i][j-1]][j-1];
            p[i][j]=p[p[i][j-1]][j-1];
        }
}

void query(int s,ll x,ll &da,ll &db)
{
    for(int i=19;i>=0;i--)
        if(p[s][i]&&disa[s][i]+disb[s][i]<=x)
        {
            da+=disa[s][i],db+=disb[s][i];
            x-=disa[s][i]+disb[s][i];
            s=p[s][i];
        }
    if(nxta[s]&&disa[s][0]<=x)da+=disa[s][0];
}

int main()
{
    //freopen("lx.in","r",stdin);
    int x,s;
    n=getint();
    for(int i=1;i<=n;i++)a[i]=(node){i,getint()};
    for(int i=n;i;i--)
    {
        S.insert(a[i]);
        if(i<n)Find(i);
    }
    Init();
    x=getint();
    int ans=0;
    ll mxa=0,mxb=0;
    for(int i=1;i<=n;i++)
    {
        ll da=0,db=0;
        query(i,x,da,db);
        if(!ans||da*mxb<mxa*db)ans=i,mxa=da,mxb=db;
    }
    cout<<ans<<'\n';
    m=getint();
    while(m--)
    {
        ll da=0,db=0;
        s=getint(),x=getint();
        query(s,x,da,db);
        cout<<da<<" "<<db<<'\n';
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值