题目描述

小 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 行驶的里程总数。

简单点说

读入n个点,点的编号是1~n,每一个点有一个高度\(h_i\),并且两个点i和j之间的距离定义为\(|h_i - h_j|\)

现在有个两个人A和B,A每次会跳到距离当前点的次小的点,而B会跳到距离当前点最小的点

现在有两个问题

1.要求从哪个点出发的,A和B所走的路程的比值最小,如果相等,则求序号最小的那个点



2.给出\(m\)个询问,每一个询问给定一个\(x_i\)个,求从这个点出发,a和b最多能走多少路程

QAQ这个题,说实话,写起来不是特别好写

最一开始看这个题

初始化是自己想到的

我们考虑对于

在排好序的序列中

距离一个点i的最小和次小的点,一定是在编号为\(i+1,i+2,i-1,i-2\)中,所以说,我们需要一个能维护这样一个前驱和后继的东西。

自然而然想到了 set!!

QwQ虽然set在noip老爷机会T飞,但是,无伤大雅呀

首先先把所有元素插入到set里,然后按照编号来先求出从每一个点A和B下一步会走到哪个点,求完就把这个点erase掉

注意!!!!! 一定要注意在地址进行++和- -时候一定不要越过\(s.end() 和 s.begin()\)的地址,不然会RE

count函数

struct Node{
int h,zuixiao,cixiao;
int id;
}; Node a[maxn]; set<int> s;
map<long long ,int > mmap;
long long sa[maxn][21];
long long sb[maxn][21];
int f[maxn][21];
int x;
int n,m;
int ha[maxn],hb[maxn]; void count(int x)
{
int tt[10];
for (int i=1;i<=10;i++) tt[i]=2e9;
set<int>::iterator it;
it=s.upper_bound(a[x].h);
if (it!=s.end())
{
tt[3]=*it;
if (it!=s.find(a[x].h))
it++;
}
if (it!=s.end())
{
if (it!=s.find(a[x].h))
tt[4]=*it;
}
it=s.find(a[x].h);
if (it!=s.begin())
{
it--;
if (it!=s.find(a[x].h))
tt[2]=*it;
}
if (it!=s.begin())
{
it--;
if (it!=s.find(a[x].h))
tt[1]=*it;
}
int min1 = 1e9,pos1=0;
for (int i=4;i>=1;i--)
{
if (tt[i]!=2e9 && min1>=abs(tt[i]-a[x].h)) min1=abs(tt[i]-a[x].h),pos1=mmap[tt[i]];
}
if (pos1!=0)
{
a[x].zuixiao=pos1;
}
int min2 = 1e9,pos2=0;
for (int i=4;i>=1;i--)
{
if (tt[i]!=2e9 && min2>=abs(tt[i]-a[x].h) && mmap[tt[i]]!=pos1) min2=abs(tt[i]-a[x].h),pos2=mmap[tt[i]];
}
if (pos2!=0)
{
a[x].cixiao=pos2;
}
//cout<<a[x].zuixiao<<" "<<a[x].cixiao<<endl;
}

这是主程序的预处理的部分

scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d",&a[i].h);
s.insert(a[i].h);
mmap[a[i].h]=i;
}
for (int i=1;i<=n;i++)
{
count(i);
s.erase(a[i].h);
}

然后我们考虑,QWQ从一个点该如何往后走呢~

emmm 直接模拟当然是会爆炸

然后发现,这个 貌似可以倍增呀

\(sa[i][j]\)表示A从i这个点,跳2^j轮的距离是多少

\(sb[i][j]\)表示B从i这个点,跳2^j轮的距离是多少

\(f[i][j]\)表示从i这个点,A和B都走了2^j轮后,到哪个点

需要注意的是,由于是从A开始走,所有在\(sb[i][0]\) 初始化的时候,是i这个点出去次小的点的最小的点的距离!

这里是初始化

 for (int i=1;i<=n;i++)
{
if (a[i].cixiao)
sa[i][0]=(long long)abs(a[i].h-a[a[i].cixiao].h);
if (a[i].cixiao && a[a[i].cixiao].zuixiao)
sb[i][0]=(long long)abs(a[a[i].cixiao].h-a[a[a[i].cixiao].zuixiao].h);// *****
f[i][0]=a[a[i].cixiao].zuixiao;
//cout<<f[i][0]<<endl;
}

更新dp数组的时候

跟....LCA差不多 对(或者说 货车运输)

for (int j=1;j<=19;j++)
{
for (int i=1;i<=n;i++)
{
f[i][j]=f[f[i][j-1]][j-1];
sa[i][j]=sa[i][j-1]+sa[f[i][j-1]][j-1];
sb[i][j]=sb[i][j-1]+sb[f[i][j-1]][j-1];
}
}

QAQemmmmmm为了以后计算起来方便,我们先写两个函数

一个是\(getfar(now,x,ansb,ansb)\)表示求出从now这个点开始走x轮,A走的距离,B走的距离,到哪个点

就是类似LCA跳的方式~感觉还是很值得纪念的

int getfar(int now,int x,long long &ansa,long long &ansb) //从now走x轮,最多能到哪
{
int len = x;
int j=0;
while (len)
{
if (len & 1)
{
ansa+=sa[now][j];
ansb+=sb[now][j];
now=f[now][j];
}
len>>=1;
j++;
if (!now) break;
}
return now;
}

另一个是\(getmax(x,len)\)是求从x走len的距离

通过二分能走几轮,然后巧妙的运用刚刚getfar函数,求出A和B走的距离,看看加起来是否小于len,如果小于,则是合法

int getmax(int x,long long len)//从x走len的距离,最多能走几轮
{
int l = 0,r=n+1,ans=0;
while (l<=r)
{
int mid = (l+r) >> 1;
long long fa=0,fb=0;
int num=getfar(x,mid,fa,fb);
if (fa+fb>len || !num){
r=mid-1;
}
else
{
ans=mid;
l=mid+1;
}
}
return ans;
}

至此,这个题已经解决了一大半了

对于第一个询问,我们可以暴力枚举点,然后求结果,但是求的时候,为了避免精度误差,我们可以考虑将分数转化一下

假设原来的答案是 \(ansa 和 ans b\)

现在的答案是\(f1和f2\)



如果

\(\frac {f1} {f2} < \frac {ansa}{ansb}\)



\(f1*ansb < f2*ansa\)

所以更新的条件可以直接写成乘法而不是除法,减少精度误差

同时如果这两个值相等,那么我们会选择\(h_i\)比较小那个

而对于第二个问题,就直接\(getmax\)然后\(getfar\)就可以

但是!!!!

一定要注意,由于是A先跳,所以在\(getfar\)之后,要判断A是否还可以跳一步,就是

if (a[now].cixiao && f1+f2+sa[now][0]<=len)
f1+=sa[now][0];

最后输出答案即可

那么这道题就解决啦!感觉还是很复杂的一道题!毕竟是noipD1T3

下面是整个的代码,其中还是有一些细节的

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<map>
#include<vector>
#include<set> using namespace std; inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
} const int maxn = 1e5+1e2; struct Node{
int h,zuixiao,cixiao;
int id;
}; Node a[maxn]; set<int> s;
map<long long ,int > mmap;
long long sa[maxn][21];
long long sb[maxn][21];
int f[maxn][21];
int x;
int n,m;
int ha[maxn],hb[maxn]; void count(int x)
{
int tt[10];
for (int i=1;i<=10;i++) tt[i]=2e9;
set<int>::iterator it;
it=s.upper_bound(a[x].h);
if (it!=s.end())
{
tt[3]=*it;
if (it!=s.find(a[x].h))
it++;
}
if (it!=s.end())
{
if (it!=s.find(a[x].h))
tt[4]=*it;
}
it=s.find(a[x].h);
if (it!=s.begin())
{
it--;
if (it!=s.find(a[x].h))
tt[2]=*it;
}
if (it!=s.begin())
{
it--;
if (it!=s.find(a[x].h))
tt[1]=*it;
}
int min1 = 1e9,pos1=0;
for (int i=4;i>=1;i--)
{
if (tt[i]!=2e9 && min1>=abs(tt[i]-a[x].h)) min1=abs(tt[i]-a[x].h),pos1=mmap[tt[i]];
}
if (pos1!=0)
{
a[x].zuixiao=pos1;
}
int min2 = 1e9,pos2=0;
for (int i=4;i>=1;i--)
{
if (tt[i]!=2e9 && min2>=abs(tt[i]-a[x].h) && mmap[tt[i]]!=pos1) min2=abs(tt[i]-a[x].h),pos2=mmap[tt[i]];
}
if (pos2!=0)
{
a[x].cixiao=pos2;
}
//cout<<a[x].zuixiao<<" "<<a[x].cixiao<<endl;
} int getfar(int now,int x,long long &ansa,long long &ansb) //从now走x轮,最多能到哪
{
int len = x;
int j=0;
while (len)
{
if (len & 1)
{
ansa+=sa[now][j];
ansb+=sb[now][j];
now=f[now][j];
}
len>>=1;
j++;
if (!now) break;
}
return now;
} int getmax(int x,long long len)//从x走len的距离,最多能走几轮
{
int l = 0,r=n+1,ans=0;
while (l<=r)
{
int mid = (l+r) >> 1;
long long fa=0,fb=0;
int num=getfar(x,mid,fa,fb);
if (fa+fb>len || !num){
r=mid-1;
}
else
{
ans=mid;
l=mid+1;
}
}
return ans;
} void solve1(int x)
{
long long ansa=1e9,ansb=0,ans=0;
for (int i=1;i<=n;i++)
{
long long f1=0,f2=0;
int len = getmax(i,x);
int now = getfar(i,len,f1,f2);
if (a[now].cixiao && f1+f2+sa[now][0]<=x){
f1+=sa[now][0];
}
if (!f1) continue;
if (ansb*f1 < ansa*f2){
ansb=f2;
ansa=f1;
ans=i;
}
else
{
if (ansb*f1 == ansa*f2 && a[ans].h<a[i].h){
ansb=f2;
ansa=f1;
ans=i;
}
}
}
cout<<ans<<endl;
} int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d",&a[i].h);
s.insert(a[i].h);
mmap[a[i].h]=i;
}
for (int i=1;i<=n;i++)
{
count(i);
s.erase(a[i].h);
}
for (int i=1;i<=n;i++){
//cout<<a[i].zuixiao<<" "<<a[i].cixiao<<endl;
}
for (int i=1;i<=n;i++)
{
if (a[i].cixiao)
sa[i][0]=(long long)abs(a[i].h-a[a[i].cixiao].h);
if (a[i].cixiao && a[a[i].cixiao].zuixiao)
sb[i][0]=(long long)abs(a[a[i].cixiao].h-a[a[a[i].cixiao].zuixiao].h);// *****
f[i][0]=a[a[i].cixiao].zuixiao;
//cout<<f[i][0]<<endl;
} for (int j=1;j<=19;j++)
{
for (int i=1;i<=n;i++)
{
f[i][j]=f[f[i][j-1]][j-1];
sa[i][j]=sa[i][j-1]+sa[f[i][j-1]][j-1];
sb[i][j]=sb[i][j-1]+sb[f[i][j-1]][j-1];
//if (sb[i][j])
//cout<<sb[i][j]<<endl;
}
}
//for (int j=0;j<=log;j++)
//{
// for (int i=1;i<=n;i++)
// {
// printf("%d %lld %lld\n",f[i][j],sa[i][j],sb[i][j]);
/// }
// }
scanf("%d",&x);
solve1(x);
scanf("%d",&m);
long long len=0;
for (int i=1;i<=m;i++)
{
long long f1=0,f2=0;
scanf("%d %lld",&x,&len);
int round=getmax(x,len);
int now = getfar(x,round,f1,f2);
if (a[now].cixiao && f1+f2+sa[now][0]<=len)
f1+=sa[now][0];
printf("%lld %lld\n",f1,f2);
}
return 0;
}

luogu1081 开车旅行2012 D1T3 (倍增,set,O2)的更多相关文章

  1. 开车旅行 2012年NOIP全国联赛提高组(倍增+set)

    开车旅行 2012年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond     题目描述 Description 小A 和小B决定利用 ...

  2. luogu1081 开车旅行 树上倍增

    题目大意 小A和小B决定利用假期外出旅行,他们将想去的城市从1到N编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市i 的海拔高度为Hi,城市i 和城市j 之间的距离 ...

  3. $Noip2012\ Luogu1081$ 开车旅行 倍增优化$ DP$

    Luogu Description Sol 1.发现对于每个城市,小A和小B的选择是固定的,可以预处理出来,分别记为ga[],gb[] 2.并且,只要知道了出发城市和出发天数,那么当前城市和小A,小B ...

  4. luogu1081 [NOIp2012]开车旅行 (STL::multiset+倍增)

    先用不管什么方法求出来从每个点出发,A走到哪.B走到哪(我写了一个很沙雕的STL) 然后把每个点拆成两个点,分别表示A从这里出发和B从这里出发,然后连边是要A连到B.B连到A.边长就是这次走的路径长度 ...

  5. [luogu1081] 开车旅行

    题面 ​ 这个题目还是值得思考的. ​ 看到这个题目, 大家应该都想到了这样一个思路, 就是把每个点能够达到的最近的和次近的点都预处理出来, 然后跑就可以了, 现在问题就是难在这个预处理上面, 我们应 ...

  6. 洛谷 P1081 开车旅行【双向链表+倍增】

    倍增数组的20和N写反了反复WAWAWA-- 注意到a和b在每个点上出发都会到一个指定的点,所以这样构成了两棵以n点为根的树 假设我们建出了这两棵树,对于第一问就可以枚举起点然后倍增的找出ab路径长度 ...

  7. 开车旅行 【NOIP2012 D1T3】

    开车旅行 [NOIP2012 D1T3] 倍增 首先令\(a[i]\)表示从i出发最近的城市下标,\(b[i]\)表示从i出发第二近的城市下标 可以维护一个\(\text{set<pair< ...

  8. Luogu 1081 【NOIP2012】开车旅行 (链表,倍增)

    Luogu 1081 [NOIP2012]开车旅行 (链表,倍增) Description 小A 和小B决定利用假期外出旅行,他们将想去的城市从1到N 编号,且编号较小的城市在编号较大的城市的西边,已 ...

  9. P1081 [NOIP2012]开车旅行[倍增]

    P1081 开车旅行    题面较为啰嗦.大概概括:一个数列,只能从一个点向后走,两种方案:A.走到和自己差的绝对值次小的点B.走到和自己差的绝对值最小点:花费为此差绝对值:若干询问从规定点向后最多花 ...

随机推荐

  1. TDSQL MySQL版基本原理-水平分表 读写分离 弹性扩展 强同步

    TDSQL MySQL版(TDSQL for MySQL)是部署在腾讯云上的一种支持自动水平拆分.Shared Nothing 架构的分布式数据库.TDSQL MySQL版 即业务获取的是完整的逻辑库 ...

  2. IP掩码的作用

    IP地址&IP掩码==网段,即,与上掩码后相同的IP属于同一网段.

  3. Python3-sqlalchemy-orm 创建多表关联表带外键

    #-*-coding:utf-8-*- #__author__ = "logan.xu" import sqlalchemy from sqlalchemy import crea ...

  4. idea无法使用中文输入法输入

    问题--idea无法使用中文输入 原因:idea本身版本过高,所以需要你强制减低它的jdk版本 解决:使用配置idea环境变量解决 ps:目前适用于任何版本的jdk和idea 步骤: 1.新建一个ID ...

  5. JVM(一)类加载器与类加载过程

    JVM是面试必面的一个知识点,也是高级程序员必备的一个技能.以下是JVM整体核心内容,包括类加载系统,运行时数据区内部结构,执行引擎,本地方法接口. 首先来学习类的加载器,虚拟机把描述类的数据从Cla ...

  6. Linux下chkconfig

    1.chkconfig命令用于检查,设置系统的各种服务! 2.chkconfig语法 chkconfig [--add] [--del] [--list] [系统服务] 或 chkconfig [-- ...

  7. Mysql - You can't specify target table '表名' for update in FROM clause 错误解决办法

    背景 在MySQL中,写SQL语句的时候 ,可能会遇到 You can't specify target table '表名' for update in FROM clause 这样的错误 错误含义 ...

  8. 运输层协议:TCP和UDP

    运输层简介 运输层的通信实体不再是主机,而是主机中的进程.运输层的通信是一台主机的进程和另一台主机的进程进行数据交换. 运输层作用 运输层向上层的应用层提供通信服务 运输层为进程提供端到端的通信 运输 ...

  9. MacOS隐藏及显示文件

    ​ 显示隐藏文件 显示所有文件 defaults write com.apple.finder AppleShowAllFiles -boolean true killall Finder 不显示隐藏 ...

  10. element后端管理布局

    <template> <el-container> <el-header> <Header></Header> <span class ...