NOIP2012 luoguP1081 开车旅行 题解

Posted eternal风度

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NOIP2012 luoguP1081 开车旅行 题解相关的知识,希望对你有一定的参考价值。

这道题是真滴火!(一晚上加一节信息课!)

先链接一下题目:luoguP1081 开车旅行

首先,这个预处理就极其变态,要与处理出每一个点往后走A会去哪里,B会去哪里。而且还必须O(nlogn)给它跑出来,反正这就要了我好久好久的时间,还没想出来!那么我们来慎重思考一下:

1.既然要让我们这么快的时间内把一个点东边的高度最近和次近找出来,只能考虑先排序。那我们就先让它以高度为关键字排一遍序,肯定还是要记录一下原先的序号的。

2.模拟一下,如果我们要找第一个点(最西边的点)的预处理,那不就是在排完序的数组中找一下它左边两个和它右边两个再比较一下找出最近和次近(这个应该不难想)。然后,如果再找第二个点的预处理,第一个点显然有可能会干扰到它,所以处理完第一个点之后我们考虑把它“删”掉,这就可以用双向链表来维护了(啦啦啦!别以为这个东西很NB,其实就是用一个l,r来记录i点排序之后左边和右边的第一个东边城市,啦啦啦!),实现还是很困难的//...冷笑...\\

对应Prepare(双向链表部分在solve()里面)函数!!!

3.预处理完我们就要维护x范围内的a开的距离和b开的距离了。其实我是想了很久之后才知道怎么用倍增的(当然是看的标签之后才知道要用倍增的(我太菜了!!!))。不管了,直接倍增吧...

f[i][j]表示从i城市出发走2*2^j(就是a,b都走2^j)步到达的城市编号。                                                                                                      disA[i][j]表示从i城市出发走2*2^j(就是a,b都走2^j)步a开了多远。                                                                                                      disB[i][j]表示从i城市出发走2*2^j(就是a,b都走2^j)步b开了多远。

对应Bz函数!!!最后倍增的查找对应getab函数!!!

4.一个小细节:以为我们是直接倍增跳a,b一起开(如上j),所以找a,b各走了多远时最后还要特判一下a是否还可以在开一轮。

上代码:

 

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<ctime>
#include<queue>
#include<stack>
#define lst long long
#define rg register
#define N 100050
#define Inf 2147483647
using namespace std;

int n,m,X0,ans=n;
struct CITY{
    lst v;
    int num,l,r;
}ljl[N];
int back[N],go[N],nA[N],nB[N];
int f[N][20];
lst disA[N][20],disB[N][20],a,b;
double minn=2147483647;

inline lst read()
{
    rg lst s=0,m=1;char ch=getchar();
    while(ch!=-&&(ch<0||ch>9))ch=getchar();
    if(ch==-)m=-1,ch=getchar();
    while(ch>=0&&ch<=9)s=(s<<3)+(s<<1)+ch-0,ch=getchar();
    return s*m;
}

inline int cmp(rg const CITY &a,rg const CITY &b){return a.v<b.v;}
inline int dis(rg int p,rg int q){return abs(ljl[p].v-ljl[q].v);}

inline int pd(rg int x,rg int y,rg int now)//x小返回1,y小返回0
{
    if(!x)return 0;//x不存在
    if(!y)return 1;//y不存在
    return dis(x,now)<=dis(y,now);//返回小一些的
}

inline void solve(rg int tt,rg int now)
{
    rg int ll=ljl[now].l,rr=ljl[now].r;
    if(pd(ll,rr,now))//左边离得近一些
        if(pd(ljl[ll].l,rr,now))//左边的左边离得近一些
            nB[tt]=back[ll],nA[tt]=back[ljl[ll].l];
        else//右边离得近一些
            nB[tt]=back[ll],nA[tt]=back[rr];
    else//右边离得近一些
        if(pd(ll,ljl[rr].r,now))//左边离得近一些
            nB[tt]=back[rr],nA[tt]=back[ll];
        else//右边的右边离得近一些
            nB[tt]=back[rr],nA[tt]=back[ljl[rr].r];
    if(ll)ljl[ll].r=rr;
    if(rr)ljl[rr].l=ll;
}

inline void Prepare()
{
    n=read();
    for(rg int i=1;i<=n;++i)ljl[i].v=read(),ljl[i].num=i;
    sort(ljl+1,ljl+n+1,cmp);//以高度为关键字排序
    for(rg int i=1;i<=n;++i)back[i]=ljl[i].num,go[back[i]]=i;//排完序之后的元素在原数组中的位置
    for(rg int i=1;i<=n;++i)ljl[i].l=i-1,ljl[i].r=i+1;
    ljl[1].l=ljl[n].r=0;
    for(rg int i=1;i<=n;++i)solve(i,go[i]);
}

inline void Bz()
{
    for(rg int i=1;i<=n;++i)
    {
        f[i][0]=nB[nA[i]];
        disA[i][0]=dis(go[i],go[nA[i]]);
        disB[i][0]=dis(go[nA[i]],go[f[i][0]]);
    }
    for(rg int j=1;j<=19;++j)
        for(rg int i=1;i<=n;++i)
        {
            f[i][j]=f[f[i][j-1]][j-1];
            disA[i][j]=disA[i][j-1]+disA[f[i][j-1]][j-1];
            disB[i][j]=disB[i][j-1]+disB[f[i][j-1]][j-1];
        }
/*    for(rg int i=1;i<=n;++i)
        for(rg int j=0;j<=3;++j)
        {
            printf("   f[%d][%d]=%d\n",i,j,f[i][j]);
            printf("disA[%d][%d]=%lld\n",i,j,disA[i][j]);
            printf("disB[%d][%d]=%lld\n",i,j,disB[i][j]);
        }
*/}

inline void getab(rg int x,rg int now)
{
    a=b=0;
    for(rg int i=19;i>=0;--i)
        if(f[now][i]&&(a+b+disA[now][i]+disB[now][i]<=x))
            a+=disA[now][i],b+=disB[now][i],now=f[now][i];
    if(nA[now]&&a+b+disA[now][0]<=x)a+=disA[now][0];
}

int main()
{
    Prepare();//预处理左右A,B的方案
//    for(rg int i=1;i<=n;++i)printf("nA[%d]=%d nB[%d]=%d\n",i,nA[i],i,nB[i]);
    Bz();//处理倍增
    X0=read(),m=read();
    for(rg int i=1;i<=n;++i)
    {
        getab(X0,i);
        if(b&&1.0*a/b<minn)
            minn=1.0*a/b,ans=i;
    }
    printf("%d\n",ans);
    for(rg int i=1;i<=m;++i)
    {
        rg int s=read(),x=read();
        getab(x,s);
        printf("%lld %lld\n",a,b);
    }
    return 0;
}

 

 

 

ojbk!!!

 

以上是关于NOIP2012 luoguP1081 开车旅行 题解的主要内容,如果未能解决你的问题,请参考以下文章

[NOIP2012] 提高组 洛谷P1081 开车旅行

$Noip2012 Luogu1081$ 开车旅行(倍增优化$ DP$)

开车旅行

luogu1081 开车旅行2012 D1T3 (倍增,set,O2)

NOIP2012开车旅行

[NOIP2012] 开车旅行