题解:P1081 [NOIP2012 提高组] 开车旅行

· · 题解

Part 1 预处理 首先要处理出小A和小B怎么走。 我们用ga(i)、gb(i)分别表示小A、小B行驶到的下一个城市 容易想到,嗯,

法一:暴力求解 枚举1-n的每一个城市,对于每个城市i,从(i+1-n)中选取距离最小的两个城市。时间复杂度 n^2.爆啦!surprise mother f**k

接下来讲讲正解,有个神奇的东西叫做#inclue<set>

它支持logn级别的插入和查询,并对内部的数进行排序

s.insert(x);//插入x s.lower_bound(x);//查找大于等于x的最小元素,返回迭代器 s.upper_bound(x);//查找大于x的最小元素,返回迭代器 所以。对于当前城市i

最近的和第二近的只会在

四个位置出现 PS:这里有个细节,他们只能向编号更大的城市走去,所以可以用个小技巧,倒着处理**。

for(int i=n; i>=1; i--) q.insert(h[i]); Part 2 动态规划

因此我们可以定义

f[i,j,k]表示从城市j出发,两人一共行驶i天,k先开车最终会到达的城市。其中k=0表示A开车,k=1表示B开车。 于是终极大魔王出现了!

倍增DP 我们将i改为一共行驶了2^i天。

于是初值:

f[0,j,0]=ga(j),f[0,j,1]=gb(j) 当i=1时,因为2^0是奇数,所以两人从j出发开2^1天所到达的城市,等于k先开2^0天,另一人再开2^0天所到达的城市

f[1,j,k]=f[0,f[0,j,k],1-k/换了个人开/] 当i>1时,你怎么折腾2^i都是偶数,所以不会换人,而两人从j出发开2^i天所到达的城市,等于等于k先开2^i-1天,k再开2^i-1天所到达的城市。

所以

f[i,j,k]=f[i-1,f[i-1,j,k],k] **这里有个小问题,需要注意超出n的边界的情况,所以需要特判 鉴于f数组,于是易得

定义da[i,j,k]表示从j城出发,由k先开,行驶2^i天,小A走的路程

当i==1时da[1,j,k]=da[0,j,k]+da[0,f[0,j,k],i-k];

否则 da[i,j,k]=da[i-1,j,k]+da[i-1,f[0,j,k],1-k];

我们定义db[i,j,k]表示从j城出发,由k先开,行驶2^i天,小B走的路程

当i==1时db[1,j,k]=db[0,j,k]+db[0,f[0,j,k],i-k];

否则 db[i,j,k]=db[i-1,j,k]+db[i-1,f[0,j,k],1-k];

时间复杂度Nlog N. 但是这里仅算出了2^i次方下的情况。于是我们需要满足求出从城市S出发最多行驶X公里时,A和B走了多远。

我们按倒叙枚举2的i次方,基于二进制划分的思想来拼凑X。

1.倒叙循环i=logN-0

2.对于每个i若两人从p出发行驶2^i天累计路程仍未超过x,则令累计路程加上x,并行驶到该城市

3.循环结束后即为所求。

最终实现了logN查询的复杂度。

然后对于问题。。。

[问题1]枚举!然后计算取比值最小的即为答案 [问题2]多次计算。

于是整个算法的时间复杂度O((N+M)log N)

代码来了:

#include<iostream>
#include<set>
#include<algorithm>
#include<cmath>
#include<string.h>
using namespace std;
//最大坑点!本题要开long long 
const long long INF=0x7fffffffffffffff/2;
double ans=10000000000;
struct node
{
  long long h;
  int id;
} h[100005],ga[100005],gb[100005];//储存城市信息 
struct point
{
  long long la,lb;
};
bool operator <(node as,node bs)
{
  if(as.h!=bs.h)return as.h<bs.h;
  else return as.id<bs.id;
}//重载运算符 
set<node> q;//以城市为单位入set 
int n,f[20][100005][3];
long long da[20][100005][3],db[20][100005][3];
bool cmp(node as,node bs)
{
  return as.h<bs.h;
}
point Calc(int p,int x)//计算以p为起点,行驶距离不超过x的路程和 
{
  long long la=0,lb=0;
  for(int i=18; i>=0; i--)//倒叙 
  {
    if(f[i][p][0]&&la+lb+da[i][p][0]+db[i][p][0]<=x)
    {
        la+=da[i][p][0];
        lb+=db[i][p][0];
        p=f[i][p][0];

    }
  }
  return {la,lb};
}
int main()
{
  scanf("%d",&n);
  q.insert({INF,0});
  q.insert({INF-1,0});
  q.insert({-INF,0});
  q.insert({-INF+1,0});//边界处理,预防预处理时越界 
  for(int i=1; i<=n; i++)
  {
    scanf("%lld",&h[i].h);
    h[i].id=i;
  }

  for(int i=n; i>=1; i--)//倒叙 
  {
    q.insert(h[i]);
    node t[5];
    t[1]=*--q.lower_bound(h[i]);
    t[2]=*--q.lower_bound(t[1]);
    t[3]=*q.upper_bound(h[i]);
    t[4]=*q.upper_bound(t[3]);//查找4个可能为最近城市
    for(int j=1; j<=4; j++)t[j].h=abs(t[j].h-h[i].h);
    sort(t+1,t+5,cmp);//排序 
    ga[i]=t[2];
    gb[i]=t[1];
  }
  for(int i=1; i<n; i++)
  {
    f[0][i][0]=ga[i].id;
    f[0][i][1]=gb[i].id;
  }
  for(int i=1; i<=18; i++)
    for(int j=1; j<=n; j++)
        if(j+(1<<i)<=n)//预防越界 
            for(int k=0; k<=1; k++)
            {
                if(i==1)f[1][j][k]=f[0][f[0][j][k]][1-k];
                else f[i][j][k]=f[i-1][f[i-1][j][k]][k];
            }
  memset(da,127/3,sizeof(da));
  memset(db,127/3,sizeof(db));//当不可到达时默认为最大 
  for(int i=1; i<=n; i++)
  {
    da[0][i][0]=ga[i].h;
    da[0][i][1]=0;
  }
  for(int i=1; i<=18; i++)
    for(int j=1; j<=n; j++)
        if(j+(1<<i)<=n)//预防越界 
            for(int k=0; k<=1; k++)
            {
                if(i==1)
                {
                    da[1][j][k]=da[0][j][k]+da[0][f[0][j][k]][1-k];
                }
                else da[i][j][k]=da[i-1][j][k]+da[i-1][f[i-1][j][k]][k];
            }
  for(int i=1; i<=n; i++)
  {
    db[0][i][0]=0;
    db[0][i][1]=gb[i].h;
  }
  for(int i=1; i<=18; i++)
    for(int j=1; j<=n; j++)
        if(j+(1<<i)<=n)//预防越界 
            for(int k=0; k<=1; k++)
            {
                if(i==1)
                {
                    db[1][j][k]=db[0][j][k]+db[0][f[0][j][k]][k^1];
                }
                else db[i][j][k]=db[i-1][j][k]+db[i-1][f[i-1][j][k]][k];
            }
  int x0,m;
  scanf("%d",&x0);
  int ddd=0;
  for(int i=1; i<=n; i++)//枚举找出最小比 
  {
    point tmp=Calc(i,x0);
    if(tmp.lb==0)continue;//防止除0 
    double ccf=(long double)tmp.la/(long double)tmp.lb;
    if(ccf<ans)ans=ccf,ddd=i;
  }
  printf("%d\n",ddd);
  scanf("%d",&m);
  for(int i=1; i<=m; i++)
  {
    int si,xi;
    scanf("%d%d",&si,&xi);
    point tmp=Calc(si,xi);//依次计算 
    printf("%lld %lld\n",tmp.la,tmp.lb);
  }
}