• luoguP1081 开车旅行 题解(NOIP2012)


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

    先链接一下题目: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
    ",i,j,f[i][j]);
                printf("disA[%d][%d]=%lld
    ",i,j,disA[i][j]);
                printf("disB[%d][%d]=%lld
    ",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
    ",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
    ",ans);
        for(rg int i=1;i<=m;++i)
        {
            rg int s=read(),x=read();
            getab(x,s);
            printf("%lld %lld
    ",a,b);
        }
        return 0;
    }

    ojbk!!!





    哪怕人间是炼狱,梦想永远是天堂
    继续走下去吧,理想永远都年轻,花儿一定会再次盛开
  • 相关阅读:
    洛谷 题解 P5595 【【XR-4】歌唱比赛】
    洛谷 题解 CF1151D 【Stas and the Queue at the Buffet】
    洛谷 题解 CF299A 【Ksusha and Array】
    仙人掌找环
    2-SAT
    带花树
    帮我背单词
    csp2019退役祭
    P5284 [十二省联考2019]字符串问题 题解
    【网络流24题】魔术球问题
  • 原文地址:https://www.cnblogs.com/cjoierljl/p/8719346.html
Copyright © 2020-2023  润新知