开车旅行(drive)

来源:NOIP2012提高组Day1

题意:

有n个城市,第i个城市可以通向所有j城市(j>i),每个城市有一个海拔h[i],两个城市之间的距离定义为d(x,y)=abs(h[x]-h[y])。
有两个人,小A和小B轮流开车,小A第一个开。
小A的开车习惯是开到与当前城市距离次小的城市。
小B的开车习惯是开到与当前城市距离最小的城市。
如果轮到某个人的时候他们开不下去了,或者开去的代价会使得总代价大于x,那么就停止驾驶。
首先给出一个x[0],求出从哪个城市出发使得cost[a]/cost[b]最小。注意如若cost[b]=0,那么这个值视为无穷大,且两个无穷大是一样大的。如果存在比值相等的情况,那么输出海拔最高的那个城市编号。
然后给出m个询问。
求对于s[i],x[i],从s[i]出发,总代价不超过x[i],a,b各走了多少距离。
注意:
1.如果对于某个城市有两个城市与它的距离一样大,那么认为海拔较低的离它近。
2.城市的海拔各不相同。

n<=100000,m<=10000,x[i],h[i]在int范围内,1<=s[i]<=n

分析

题面好长
首先显而易见的可以发现对于同一个点同一个人,走到的结果肯定是一样的。x对走向的下一个点是没有影响的。也就是说从某个点开始走的路径是唯一的,只是x决定了这条路能走多长。

第一问我没有想到什么特别好的做法,只能选择暴力枚举每一个点来找最优的。

对于70%的数据的做法。
n<=1000,m<=10000
可以用O( n2 )的方法求出对于每个点向后走向的最小点和次小点是哪个,然后对于第一问直接枚举每个点,然后对于每个(s,x)都用O(n)的跑一次。事实上时间会比n要小一些不过还是肯定会T的根本不用想…第二问是同理的。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define M 100005
#define ll long long
#define pll pair<long long,long long>
#define A first
#define B second
#define mp make_pair
using namespace std;

void read(int &x){
    x=0; char c=getchar(); int f=1;
    for (; c<'0'; c=getchar())if (c=='-')f=-1;
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0');  
    x*=f;
}
int h[M],n,m,X[M],S[M];
struct AAA{
    ll mx[M],mx1[M];
    pll get(ll s,ll x){
//      printf("---%d %d---\n",s,x);
//      printf("%d",s);
//      printf("%lld %lld\n",s,x);
        ll coa=0,cob=0,nex,co;
        bool f=0;
        for (; x&&s;){
            if (f){
                nex=mx[s];  co=abs(h[nex]-h[s]);
                if (!nex){x=0; continue;}
                if (nex&&co<=x)x-=co,cob+=co;
                else x=0;
                s=nex;
            }
            else{
                nex=mx1[s]; co=abs(h[nex]-h[s]);
                if (!nex){x=0; continue;}
                if (nex&&co<=x)x-=co,coa+=co;
                else x=0;
                s=nex;
            }
            f=!f;
        }
        return mp(coa,cob); 
    }

    void solve(){
        int i,j;
        for (i=1; i<=n; i++){
            mx[i]=mx1[i]=0;
            for (j=i+1; j<=n; j++){
                int d=abs(h[i]-h[j]);
                if (mx[i]==0||d<abs(h[i]-h[mx[i]])||(d==abs(h[i]-h[mx[i]])&&h[j]<h[mx[i]])){mx1[i]=mx[i]; mx[i]=j;}
                else if(mx1[i]==0||d<abs(h[i]-h[mx1[i]])||(d==abs(h[i]-h[mx1[i]])&&h[j]<h[mx1[i]]))mx1[i]=j;
            }
//          printf("%lld %lld\n",mx[i],mx1[i]);
        }   
        ll res=-1;
        ll ca=0,cb=0;
        for (i=1; i<=n; i++){
            pll now=get(i,X[0]);
            if (now.B){if (res==-1||now.A*cb<now.B*ca||(now.A*cb==now.B*ca&&h[i]>h[res])){res=i;ca=now.A;cb=now.B;}}
            else {if (res==-1||(h[i]>h[res]&&cb==0))res=i;}
        }
        printf("%lld\n",res);
        for (i=1; i<=m; i++){
            pll now=get(S[i],X[i]);
            printf("%lld %lld\n",now.A,now.B);
        }
    }   
}p70;

int main(){
    int i;
//  freopen("drive.in","r",stdin);
//  freopen("drive.out","w",stdout);
    read(n);
    for (i=1; i<=n; i++)read(h[i]);
    read(X[0]);
    read(m);
    for (i=1; i<=m; i++)read(S[i]),read(X[i]);
    p70.solve();
    return 0;
}

对于100%的数据的做法
首先来看一下上面的做法,复杂度主要是在两个方面,一个是对于每一个点求之后的最小值和次小值,一个是对于每个(s,x)都要用O(n)的复杂度来模拟,太费时间了。

对于第一个求最小值和次小值的优化。似乎有两种方法。
1.用数据结构维护,快速的找比它小的最大的点和比它大的最小的点,还有次大和次小。不过我并没有写这个。
2.链表,排序依次然后将城市依次按海拔顺序连起来,每次求完一个点就把它给删了。且每个点的答案只会在它的左节点,左节点的左节点,右节点和右节点的右节点中出现,判断一下即可。

对于第二个的优化。可以采用倍增的方法。
设to[x][k]表示从x出发,走了 2k 轮后到达的位置。
co[x][k].A表示从x出发,走了 2k 轮后小A的路程。
co[x][k].B表示从x出发,走了 2k 轮后小B的路程。
然后模拟的复杂度就降为O(log n)了。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define M 100005
#define ll long long
#define pll pair<long long,long long>
#define A first
#define B second
#define inf (1e18)
#define mp make_pair
using namespace std;

void read(ll &x){
    x=0; char c=getchar(); int f=1;
    for (; c<'0'; c=getchar())if (c=='-')f=-1;
    for (; c>='0'; c=getchar())x=(x<<3)+(x<<1)+(c^'0'); 
    x*=f;
}
ll h[M],to[M][18],n,m,X[M],S[M],Mp[M];
pll co[M][18];
struct AC{ll x,ne,be;}c[M];
pll b[M],a[M];

void Max(ll &x,ll y){if (x<y)x=y;}
void Min(ll &x,ll y){if (x>y)x=y;}

int ch(int x){
    //二分查找 用于离散(似乎手写比lb要快...) 
    int l=1,r=n,mid;
    for (; ;){
        mid=(l+r)>>1;
        if (b[mid].A==x)return mid;
        if (b[mid].A<x)l=mid+1;
        else r=mid-1;       
    }
}

void del(int x){
    //删除链表中的节点 
    c[c[x].be].ne=c[x].ne;
    c[c[x].ne].be=c[x].be;
    c[x].ne=c[x].be=0;
    c[0].be=c[0].ne=c[n+1].be=c[n+1].ne=0;
}

bool ch(int y,int z,int x){
    if (z==0)return 0;
    if (y==0||y==n+1)return 1;
    x=h[x]; y=h[y]; z=h[z];
    int l1=abs(b[y].A-b[x].A),l2=abs(b[z].A-b[x].A);
    if (l1<l2)return 0;
    if (l1>l2)return 1;
    //对于距离相等的情况,海拔低的比较近 
    return y>z;
}

pll get(ll s,ll x){
    ll ca=0,cb=0,k;
    //倍增模拟 
    for (k=16; k>=0&&x&&s; k--)if (to[s][k]&&co[s][k].A+co[s][k].B<=x){
        x-=co[s][k].A+co[s][k].B;
        ca+=co[s][k].A;
        cb+=co[s][k].B;
        s=to[s][k];
    }
    //小A可能可以再走一次 
    if (s&&co[s][0].A<=x){x-=co[s][0].A; ca+=co[s][0].A;}
    return mp(ca,cb);   
}

int main(){
    int i;
//  freopen("drive.in","r",stdin);
//  freopen("drive.out","w",stdout);
    read(n);
    for (i=1; i<=n; i++)read(h[i]),b[i].A=h[i],b[i].B=i;
    sort(b+1,b+n+1);
    for (i=1; i<=n; i++)h[i]=ch(h[i]),Mp[h[i]]=i;
    c[0].x=inf; c[n+1].x=inf;
    for (i=1; i<=n; i++){
        c[b[i].B].x=i;
        c[b[i].B].be=b[i-1].B;
        c[b[i].B].ne=b[i+1].B;
    }
    int be1,be2,ne1,ne2;

    for (i=1; i<=n; i++){
        be1=c[i].be;
        be2=c[c[i].be].be;
        ne1=c[i].ne;
        ne2=c[c[i].ne].ne;
        int res1=0,res2=0;
        if (ch(res1,be1,i)){res2=res1; res1=be1;}else if (ch(res2,be1,i))res2=be1;
        if (ch(res1,be2,i)){res2=res1; res1=be2;}else if (ch(res2,be2,i))res2=be2;
        if (ch(res1,ne1,i)){res2=res1; res1=ne1;}else if (ch(res2,ne1,i))res2=ne1;
        if (ch(res1,ne2,i)){res2=res1; res1=ne2;}else if (ch(res2,ne2,i))res2=ne2;
        if (res1>n)res1=0; if (res2>n)res2=0;
        a[i].A=res1; a[i].B=res2;
        del(i);
    }

    b[0].A=inf; b[n+1].A=inf;
    for (i=1; i<=n;i++){
        to[i][0]=a[a[i].B].A;
        co[i][0].A=abs(b[h[i]].A-b[h[a[i].B]].A);
        co[i][0].B=abs(b[h[a[i].B]].A-b[h[a[a[i].B].A]].A);
    }

    int k;
    for (k=1; k<17; k++){
        for (i=1; i<=n; i++){
            to[i][k]=to[to[i][k-1]][k-1];
            co[i][k].A=co[i][k-1].A+co[to[i][k-1]][k-1].A;
            co[i][k].B=co[i][k-1].B+co[to[i][k-1]][k-1].B;  
        }
    }

    ll x,s;
    read(x);
    int res=-1;
    ll ca=0,cb=0;
    for (i=1; i<=n; i++){
        pll now=get(i,x);
        if (now.B){if (res==-1||now.A*cb<now.B*ca||(now.A*cb==now.B*ca&&h[i]>h[res])){res=i;ca=now.A;cb=now.B;}}
        else {if (res==-1||(h[i]>h[res]&&cb==0))res=i;}
    }
    printf("%d\n",res);

    read(m);
    for (i=1; i<=m; i++){
        read(s); read(x);
        pll now=get(s,x);
        printf("%lld %lld\n",now.A,now.B);
    }
    return 0;
}

吐槽

码量好像有点大啊…而且再加上前一题有个高精度除法…我估计比赛就算知道正解也未必敲的出来
而且因为嫌麻烦好多不需要long long的也用long long了…不过实在是懒得修改了。
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值