洛谷 P1081 开车旅行 [noip2012] (倍增lca+链表优化)

题目描述

小 A 和小 B 决定利用假期外出旅行,他们将想去的城市从 1 到 N 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为Hi,城市 i 和城市 j 之间的距离 d[i,j]恰好是这两个城市海拔高度之差的绝对值,即d[i,j] = |Hi− Hj|。 旅行过程中,小 A 和小 B 轮流开车,第一天小 A 开车,之后每天轮换一次。他们计划选择一个城市 S 作为起点,一直向东行驶,并且最多行驶 X 公里就结束旅行。小 A 和小 B的驾驶风格不同,小 B 总是沿着前进方向选择一个最近的城市作为目的地,而小 A 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 X 公里,他们就会结束旅行。

在启程之前,小 A 想知道两个问题:

对于一个给定的 X=X0,从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。
对任意给定的 X=Xi和出发城市 Si,小 A 开车行驶的路程总数以及小 B 行驶的路程总数。

输入输出格式

输入格式:

第一行包含一个整数 N,表示城市的数目。

第二行有 N 个整数,每两个整数之间用一个空格隔开,依次表示城市 1 到城市 N 的海拔高度,即 H1,H2,……,Hn,且每个 Hi都是不同的。

第三行包含一个整数 X0。

第四行为一个整数 M,表示给定 M 组 Si和 Xi。

接下来的 M 行,每行包含 2 个整数 Si和 Xi,表示从城市 Si出发,最多行驶 Xi公里。

输出格式:

输出共 M+1 行。

第一行包含一个整数 S0,表示对于给定的 X0,从编号为 S0的城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小。

接下来的 M 行,每行包含 2 个整数,之间用一个空格隔开,依次表示在给定的 Si和

Xi下小 A 行驶的里程总数和小 B 行驶的里程总数。

算法

以后补吧
根据海拔升序排列
记录每一个点为基准,海拔距离最近的,以及海拔距离次近的,如果光这样很好弄,但是它还有一个要求:一直向东行驶,也就是说你还得判断排到的i点是否是当前点右边的???

考虑把排序后的节点顺序当作链表:
有没有特殊点,没错,最西边(左边)的点在链表里的所有相邻点都在地理位置的东边(右边),处理完就删除这个点,其实也就是把俩端的点直接相连即可。

然后如何处理出第一近第二近呐?代码复杂,其实思路很简单,
对于当前节点i,设链表里左边两个从右到左为 L1L2 L 1 、 L 2 右边两个节点从左到有为 R1R2 R 1 、 R 2
对于最近点从 L1R1 L 1 和 R 1 中选
如果选择 L1 L 1 那么第二近的就是从 L2R1 L 2 和 R 1 中选小的
如果选择 R1 R 1 那么第二近的就是从 L1R2 L 1 和 R 2 中选小的
顺便判断一下选上第一近的没,没选上就是另一个。

根据原题意设置一下初始两个人的第一步路程长
然后倍增出父亲数组,两个人的路程数组,听跳跃的,但是画个图绝对明白了

然后根据两个问题的意思”暴力“就好了

注意

对于一个弱弱来说,额,这道题算不上水题,帮助我很好的运用了lca
因为倍增lca嘛““很容易就觉得结果是对的,过程是有跳跃的,就感觉处理不了过程了
其实不是的。

这道题特色就是
两个人共同走lca
但是分别记录自己的路径,
双人lca

坑就是需要考虑其中一个人可以多走一下下

绕的地方就是排序后,记录各种数据是采用排序前标号,还是排序后标号为主。很绕很绕

代码

以排序后编号为主

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
#define inf 1<<31-1;
//#define a1 aa_1
//#define b1 bb_1
int pre[110000],nxt[110000],mrn1[110000],mrn2[110000],a[110000][22],b[110000][22],f[110000][22],head[110000];//data[110000],
int n;
struct citys{int id;ll h;}c[110000];
bool cmp(const citys s,const citys t){return s.h<t.h;}
void prepare(int x)
{
    /*if(pre[x])mrn1[x]=pre[x];
    if(nxt[x]&&(c[nxt[x]].h-c[x].h<=c[x].h-c[pre[x]].h||!pre[x]))mrn1[x]=nxt[x];
    if(mrn1[x]==pre[x])
    {
        if(nxt[x])mrn2[x]=nxt[x];
        if(pre[pre[x]]&&(c[x].h-c[pre[pre[x]]].h<=c[nxt[x]].h-c[x].h||!nxt[x]))mrn2[x]=pre[pre[x]];
    }
    else
    {
        if(pre[x])mrn2[x]=pre[x];
        if(nxt[nxt[x]]&&(c[nxt[nxt[x]]].h-c[x].h<=c[x].h-c[pre[x]].h||!pre[x]))mrn2[x]=nxt[nxt[x]];
    }
    nxt[pre[x]]=nxt[x];
    pre[nxt[x]]=pre[x];*/
     mrn1[x] = pre[x] && (c[x].h - c[pre[x]].h <= c[nxt[x]].h - c[x].h || !nxt[x]) ? pre[x]:nxt[x];
    if(mrn1[x] == pre[x])
        mrn2[x] = pre[pre[x]] && (c[x].h - c[pre[pre[x]]].h <= c[nxt[x]].h - c[x].h || !nxt[x]) ? pre[pre[x]] : nxt[x];
    else
        mrn2[x] = pre[x] && (c[x].h - c[pre[x]].h <= c[nxt[nxt[x]]].h - c[x].h || !nxt[nxt[x]]) ? pre[x] : nxt[nxt[x]];
    nxt[pre[x]] = nxt[x];
    pre[nxt[x]] = pre[x];
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&c[i].h);c[i].id=i;
        nxt[i]=i+1;pre[i]=i-1;
        if(i==n)nxt[i]=0;
    }
    sort(c+1,c+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        head[c[i].id]=i;
    }
    for(int i=1;i<=n;i++)
        prepare(head[i]);
    for(int i=1;i<=n;i++)
    {
    //a first b second
    //a 第二近  b 最近
        if(mrn2[i])a[i][0]=abs(c[mrn2[i]].h-c[i].h);
        if(mrn1[i])b[i][0]=abs(c[mrn1[mrn2[i]]].h-c[mrn2[i]].h);
        f[i][0]=mrn1[mrn2[i]];  
    }
    for(int j=1;j<=20;j++)
    {
        for(int i=1;i<=n;i++)
        {
            f[i][j]=f[f[i][j-1]][j-1];
            a[i][j]=a[i][j-1]+a[f[i][j-1]][j-1];
            b[i][j]=b[i][j-1]+b[f[i][j-1]][j-1];
        }
    }
    int x,u;
    scanf("%d",&x);
    double temp,mirn=inf;int z,xa,xb,ans;
    for(int i=1;i<=n;i++)
    {
        xa=xb=0;z=x;u=head[i];//a路程,b路程,还可以走的路程,出发点的替身
        for(int j=20;j>=0;j--)
        if(z-a[u][j]-b[u][j]>=0 && f[u][j])
        {
            z=z-a[u][j]-b[u][j];
            xa+=a[u][j];
            xb+=b[u][j];
            u=f[u][j];
        }
        if(z-a[u][0]>=0&&mrn2[u])xa+=a[u][0];
        //捆绑走整数个,但是接下来a优先一步,说不定还可以走
        if(xb==0){temp=inf;}//该次循环的答案
        else temp=(double)xa/xb;
        if(temp<mirn||(temp==mirn&&c[head[i]].h>c[head[ans]].h))//更小的情况||海拔低的情况
        {
            mirn=temp;ans=i;
        }
    }
    printf("%d\n",ans);

    int whole;scanf("%d",&whole);
    while(whole--)
    {
        scanf("%d%d",&u,&x);
        xa=xb=0;z=x;u=head[u];
        for(int j=20;j>=0;j--)
        if(z-a[u][j]-b[u][j]>=0&&f[u][j])
        {
            z=z-a[u][j]-b[u][j];
            xa+=a[u][j];
            xb+=b[u][j];
            u=f[u][j];
        }
        if(z-a[u][0]>=0&&mrn2[u])xa+=a[u][0];
        printf("%d %d\n",xa,xb);
    }
    return 0;
}

RE了一个点,但是我输出来是一样的啊““““`

in:
5
-1000000000 0 -999999999 999999999 1000000000
1000000000
7
1 1000000000
2 1000000000
3 1000000000
4 1000000000
5 1000000000
1 2
2 3


out
2
1000000000 0
999999999 1
0 0
0 0
0 0
0 0
0 0

偷偷附一个0分的以排序前编号为主的算法

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
#define inf 1<<31-1;
//#define a1 aa_1
//#define b1 bb_1
int pre[110000],nxt[110000],mrn1[110000],mrn2[110000],fa[110000][22],fb[110000][22],f[110000][22],data[110000];
int n;
struct citys{int id,h;}a[110000];
bool cmp(const citys s,const citys t){return s.h<t.h;}
void prepare(int u,int i)
{   // p n
    if(pre[u])mrn1[i]=a[pre[u]].id;
    if((nxt[u]&&a[u].h-a[pre[u]].h>a[nxt[u]].h-a[u].h)||!pre[u])mrn1[i]=a[nxt[u]].id;
    if(mrn1[i]==a[pre[u]].id)
    {
        //pp n
        if(nxt[u])mrn2[i]=a[nxt[u]].id;
        if((pre[pre[u]]&&a[u].h-a[pre[pre[u]]].h<a[nxt[u]].h-a[u].h)||!nxt[u])mrn2[i]=a[pre[pre[u]]].id;
    }
    else
    {
        //p nn
        if(pre[u])mrn2[i]=a[pre[u]].id;
        if((nxt[nxt[u]]&&a[nxt[nxt[u]]].h-a[u].h<a[u].h-a[pre[u]].h)||!pre[u])mrn2[i]=a[nxt[nxt[u]]].id;
    }
    nxt[pre[u]]=nxt[u];
    pre[nxt[u]]=pre[u];
}
void init()
{
    //a first b second
    //a 第二近  b 最近
    for(int i=1;i<=n;i++)
    {
        if(mrn2[i])fa[i][0]=abs(data[mrn2[i]]-data[i]);
        if(mrn1[i])fb[i][0]=abs(data[mrn1[mrn2[i]]]-data[mrn2[i]]);
        f[i][0]=mrn1[mrn2[i]];
    }
}
void getst()
{
    init();
    for(int j=1;j<=20;j++)
        for(int i=1;i<=n;i++)   
        {
            f[i][j]=             f[f[i][j-1]][j-1];
            fa[i][j]=fa[i][j-1]+fa[f[i][j-1]][j-1];
            fb[i][j]=fb[i][j-1]+fb[f[i][j-1]][j-1];

        }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].h);a[i].id=i;data[i]=a[i].h;
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        if(i==n)nxt[i]=0;
        else
        {pre[i]=i-1;nxt[i]=i+1;}
    }
    for(int i=1;i<=n;i++)
        prepare(i,a[i].id);
    //for(int i=1;i<=n;i++)
    //cout<<i<<" "<<pre[i]<<" "<<nxt[i]<<" "<<endl;
    getst();

    /
    int x0;scanf("%d",&x0);
    double mirn=inf;int ans=0;
    for(int i=1;i<=n;i++)
    {
        int aa_1=0,bb_1=0,z=x0,u=i;//a路程,b路程,还可以走的路程,出发点的替身
        for(int j=20;j>=0;j--)//倍增思想
        {
            if(f[u][j] && z-fa[u][j]-fb[u][j]>=0)//???,可以走
            {
                aa_1+=fa[u][j],bb_1+=fb[u][j];
                z=z-fa[u][j]-fb[u][j];
                u=f[u][j];
            }
        }
        if(z-fa[u][0]>=0&&mrn2[u]){aa_1+=fa[u][0];}//捆绑走整数个,但是接下来a优先一步,说不定还可以走

        double temp;//该次循环的答案
        if(bb_1==0) {temp=inf;}
        else 
            {temp=(double)(aa_1/bb_1);}
        if(mirn>temp||(temp==mirn&& data[i]<data[ans]))//更小的情况||海拔低的情况
        {
            ans=i;mirn=temp;
        }
    }
    printf("%d\n",ans);
    
    int q,s,x1;scanf("%d",&q);
    while(q--)
    {
        scanf("%d%d",&s,&x1);
        int aa_1=0,bb_1=0;
        for(int j=20;j>=0;j--)
        {
            if(f[s][j]&&x1-fa[s][j]-fb[s][j]>=0)
            {
                x1=x1-fa[s][j]-fb[s][j];
                aa_1+=fa[s][j];
                bb_1+=fb[s][j];
                s=f[s][j];
            }
        }
        if(x1-fa[s][0]>=0&&mrn2[s])aa_1+=fa[s][0];
        printf("%d %d\n",aa_1,bb_1);                    
    }
    /*for(int i=1;i<=n;i++)
    {
        cout<<"~~~~~~~~~~"<<i<<"~~~~~~~~~~"<<endl;
        for(int j=0;j<=20;j++)
        {
            cout<<fa[i][j]<<" "; 
        }
        cout<<endl;
        for(int j=0;j<=20;j++)
        {
            cout<<fb[i][j]<<" "; 
        }
        cout<<endl;
    }*/
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值