P1081 开车旅行

传送门

 

用倍增的思想

设 A[ i ] 表示A在 i 位置走一步到达的城市以及经过的路程(这里我用结构体存A[ i ]),B同理

设 f [ i ] [ j ] 表示从 i 位置出发,走 $2^j$ 轮后到达的城市(一轮即AB各走一次)

dis[ i ] [ j ] 表示从 i 位置出发,走 $2^j$ 轮后经过总路程

da [ i ] [ j ] 表示从 i 位置出发,走 $2^j$ 轮后A经过路程,db同理

然后就可以愉快地xiagao

但是现在有一个问题,怎么预处理出 A[ i ],B[ i ]

把城市编号从大到小加入 set,每次加入一个城市前先求出 set 内离他最近的城市和第二近的城市

如果按高度排序后此高度排名为 i ,那么最近的城市就在 i+1 和 i-1 中,次近的城市就在 i+1,i+2,i-1,i-2 中

这个可以用 lower_bound 求出

然后就可以搞了

思维难度不大,具体实现起来一堆细节恶心得一批

别忘了可能AB一人走一步走不了,但是A单独可以多走一步的情况

关于题目的第一个问题,如果B的路程为0,那么就算A也为0 比值仍为INF

一定仔细看题目啊,很多细节啊

下面附上我那压行严重的巨丑代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
using namespace std;
typedef long long ll;//有些变量该开long long 果断开
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=1e5+7;
const ll INF=5e9+7;
int n,m,x0;
struct node
{
    ll h; int id;//高度,以及编号
    node () { h=0; id=0; }
    inline bool operator < (const node &tmp) const {
        return h<tmp.h;
    }
}A[N],B[N],d[N];//在A[i]B[i]中则是距离和编号
multiset <node> s;
multiset <node>::iterator it;
int f[N][22];
ll dis[N][22],da[N][22],db[N][22];
void pre()
{
    node GG;
    GG.h=INF; s.insert(GG); s.insert(GG); s.insert(GG); s.insert(GG);
    GG.h=-INF; s.insert(GG); s.insert(GG); s.insert(GG); s.insert(GG);//防止指针越界
    node mx,mxx;/*mx存最近,mxx存次近*/ ll t=0,aa=0,bb=0;
    for(int i=n;i;i--)
    {
        it=s.lower_bound(d[i]);

        aa=(*it).h; mx.h=aa-d[i].h; mx.id=(*it).id; it--;
        bb=(*it).h; mxx.h=d[i].h-bb; mxx.id=(*it).id;
        if(mxx.h<mx.h||(mxx.h==mx.h&&aa>bb)) swap(mx,mxx);//注意如果相等也要判一下
        if(!mx.id) mx.h=0;/*如果超出边界了路程统一为0*/ B[i]=mx;

        it--; t=d[i].h-(*it).h; if(t<mxx.h || (mxx.h==t&&(*it).h<d[mxx.id].h) ) mxx.h=t,mxx.id=(*it).id;//记得相等也要判一下
        it++; it++; it++; t=(*it).h-d[i].h; if(t<mxx.h || (mxx.h==t&&(*it).h<d[mxx.id].h) ) mxx.h=t,mxx.id=(*it).id;//记得相等也要判一下
        if(!mxx.id) mxx.h=0;/*同样判一下是否超出边界*/ A[i]=mxx;

        s.insert(d[i]);//别忘了加入d[i]
    }
    for(int i=1;i<=n;i++)//预处理倍增数组
    {
        f[i][0]=B[A[i].id].id;//计算f[i][0]
        if(f[i][0])//如果没出边界就正常处理
        {
            da[i][0]=A[i].h; db[i][0]=B[A[i].id].h;
            dis[i][0]=da[i][0]+db[i][0];
        }
        else da[i][0]=db[i][0]=dis[i][0]=INF;//不然设成INF
    }
    for(int k=1;k<=20;k++)
        for(int i=1;i<=n;i++)
        {
            f[i][k]=f[f[i][k-1]][k-1];
            if(f[i][k])//同样要判是否出边界
            {
                da[i][k]=da[i][k-1]+da[f[i][k-1]][k-1];
                db[i][k]=db[i][k-1]+db[f[i][k-1]][k-1];
                dis[i][k]=dis[i][k-1]+dis[f[i][k-1]][k-1];
            }
            else da[i][k]=db[i][k]=dis[i][k]=INF;
        }
}
int ansa,ansb;//存AB的路程
inline void slove(int pos,int x)//给定出发点和最大总路程求AB两人的路程
{
    ansa=ansb=0;//初始为0
    for(int i=20;i>=0;i--)//倍增求路程
    {
        if(x-dis[pos][i]<0) continue;
        ansa+=da[pos][i]; ansb+=db[pos][i];//更新ansa,ansb
        x-=dis[pos][i]; pos=f[pos][i];//别忘了更新x和pos
    }
    if(x>=A[pos].h) ansa+=A[pos].h;//最后一定要特判A单独多走一步
}
void slove_problemA()//处理第一个问题
{
    x0=read();
    int pos=0; double ans=INF,res=0;
    for(int i=1;i<=n;i++)
    {
        slove(i,x0);
        if(!ansb) res=INF;//判一下B路程为0
        else res=(double)ansa/(double)ansb;//ansa和ansb一定要先转double!!因为这个被坑了一天
        if( res<ans || (res==ans&&d[i].h>d[pos].h) )//注意如果相等也要判一下
            ans=res,pos=i;
    }
    printf("%d\n",pos);
}
void slove_problemB()//处理第二个问题
{
    m=read(); int s=0,x=0;
    while(m--)
    {
        s=read(); x=read();
        slove(s,x);
        printf("%d %d\n",ansa,ansb);
    }
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++) d[i].h=read(),d[i].id=i;//d[i].id初始为i
    pre();
    slove_problemA();
    slove_problemB();
    return 0;
}

 

转载于:https://www.cnblogs.com/LLTYYC/p/9860441.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值