[NOIP 2012 提高组] 开车旅行

前言

作为一个初二的蒟蒻 受教练之令 写的这个题目的做法

题目

[NOIP 2012 提高组] 开车旅行

luogu

题意

按照题目给定的行驶规律(小A先走 小B后走轮换开车 (一直从西往东开)小A每次选择前面与当前城市海拔高度差的绝对值次小的城市 小B每次选择前面与当前城市海拔高度差的绝对值最小的城市 )和最大路程限制下 有两个问题

1 . 对于限制了的总路程\(x_0\)从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。

2 . 对于任意城市 \(x_i\)和出发城市 \(s_i\) , 小 A 开车行驶的路程总数以及小 B 行驶的路程总数。

思考一下

暴力

根据题目内容 我们首先想一下直接暴力怎么做

我们直接按照A和B的这个行驶规律模拟

那么每一次的询问我们需要干什么呢?

先算出当前 小A or 小B 需要走到哪一个城市 有一个\(n\)的复杂度

然后最坏情况一次查询会从\(1\)~\(n\) 又是\(n\)的复杂度

最后我们发现时间复杂度来到了惊人的\(O(mn^2)\)

\(1\le n,m \le 10^5\)\(-10^9 \le h_i≤10^9\)\(1 \le s_i \le n\)\(0 \le x_i \le 10^9\)

看一眼数据范围两眼一黑

优化

不难发现 这里最好优化的地方就是对于每一个点位 小A走到的下一个点和小B走到的下一个点

如果我们预处理出来了计算当前 小A or 小B 需要走到哪一个城市就降到了惊人的\(O(1)\)

那么总复杂度降到了惊人的\(O(mn)\)

简直是一次巨大的进步!!!

但是还是会超时

\(m\)的查询应该是省不了了

那么我们就优化这里路径的\(n\) !!!

因为 这个题目在倍增dp里面的 我们必须使用一个优化到\(O(mlog_n)\)的算法

而这里每一次的计算都可以从前面的状态转移过来

那么我们自然就想到了倍增dp这个可(du)爱(liu)的算法

实现

我们先来看第一个优化 与处理一下这里的每一个点AB分别对应的下一个点的下标

我们可以使用set这个神奇的stl函数

\(B_i\)找一个j(i<jn),使得\(∣h_i-h_j∣\)最小,而\(A_i\)就是使得\(∣h_i-h_j∣\)次小

\(B_i\)就是离当前的\(i\)的高度最接近的城市,我们先给每个城市按高度排个序,这样的话,排序之后要求的\(B_i\)就是\(i-1\)\(i+1\)中,和\(i\)的高度差更小的那个

\(A_i\)同理 \(A_i\)肯定是\(i-2\),\(i-1\),\(i+1\),\(i+2\)其中之一,这四个里面肯定有一个是$$B_i,再从剩下三个里面找个小的就是\(A_i\)

直接看代码吧

void ycl(){
	set<int> s;
	A[n]=-1; //A到的下一个点的id
	B[n]=-1;//同上
	sA[n]=0;//A到下一个点的路径长度(顺便算一下)
	sB[n]=0;//同上
	s.insert(h[n]);//从后往前扫   先把对后一个加进去
	for(int i=n-1;i>=1;i--){
		auto it=s.lower_bound(h[i]);//找到当前小于等于(这里就没有等于了 因为题目保证互不相同)h[i]的最大的数的下标
		int cnt=0;//找到几个合法的
		int ans[5];//存一下这几个合法的  后面直接排序选择
		if(it!=s.end()){ //找到了
			ans[++cnt]=*it;//加入
			auto lit=next(it,1);//看它的下一个
			if(lit!=s.end()){//下一个合法
				ans[++cnt]=*lit;//加入
			}
		}
		if(it!=s.begin()){//和上面的差不多
			auto lit=prev(it,1);
			ans[++cnt]=*lit;
			if(lit!=s.begin()){
				auto llit=prev(lit,1);
				ans[++cnt]=*llit;
			}
		}
		for(int j=1;j<=cnt;j++){//idd是unordered_map存的每个高度对应的下标
			ans[j]=iid[ans[j]];//转成下标
		}
		sort(ans+1,ans+cnt+1,[&](int a,int b){//题目要求排序
			if(abs(h[a]-h[i])==abs(h[b]-h[i])){
				return h[a]<h[b];
			}
			return abs(h[a]-h[i])<abs(h[b]-h[i]);
		});
		A[i]=-1,B[i]=-1;
		sA[i]=0,sB[i]=0;
		if(cnt>=1){//有1个B就有合法的选择
			B[i]=ans[1];
			sB[i]=abs(h[B[i]]-h[i]);
		}if(cnt>=2){//2个A就有
			A[i]=ans[2];
			sA[i]=abs(h[A[i]]-h[i]);
		}
		s.insert(h[i]);//把h[i]加进去 下一次查询用
	}
	return;
}

接下来是重头戏 这里的倍增dp到底应该怎么写???

首先我们每一次转移的时候需要哪些东西

先开车的人,出发点,行驶的天数

那么我们的dp就可以用一个三维数组定义出来了

\[dp[k][i][f] \]

表示 在在第\(i\)个城市开始 已经行驶了\(2^k\) 天 最开始是谁开车(0是A 1是B)

走到的城市

而sa和sb数组同上面的定义 只不过存进去的是A行驶的路程和B行驶的路程

那么我们的初始化应该怎么初始化呢??

首先是dp数组 不难发现

\[dp[0][i][0]=A[i] \]

\[dp[0][i][1]=B[i] \]

(\(2^0=1\),开一天就是刚刚与处理过后的A,B数组)

而这里的sA,sB数组

\[sa[0][i][0]=sA[i] \]

\[sb[0][i][1]=sB[i] \]

(\(2^0=1\),开一天就是刚刚与处理过后的sA,sB数组)

那么接下来就是愉快的状态转移

先看\(k!=1\)的情况

我们发现\(2^k\)次和\(2^{k-1}\)次中间相差\(2^{k-1}\)次只要\(k!=1\) 就相差偶数次

上一次是谁开车这一次就是谁开车

那么我们的转移就非常简单了

\[dp[k][i][0]=dp[k-1][dp[k-1][i][0]][0] \]

\(i\)\(k\)步等于从\(i\)\(k-1\)步的地方再走\(k-1\)

那么同理

\[dp[k][i][1]=dp[k-1][dp[k-1][i][1]][1] \]

\[sa[k][i][0]=sa[k-1][i][0]+sa[k-1][dp[k-1][i][0]][0] \]

\[sa[k][i][1]=sa[k-1][i][1]+sa[k-1][dp[k-1][i][1]][1] \]

\[sb[k][i][0]=sb[k-1][i][0]+sb[k-1][dp[k-1][i][0]][0] \]

\[sb[k][i][1]=sb[k-1][i][1]+sb[k-1][dp[k-1][i][1]][1] \]

那么下一个\(k=1\)咋办呢 反一下就行了

\[dp[k][i][0]=dp[k-1][dp[k-1][i][0]][1] \]

\[dp[k][i][1]=dp[k-1][dp[k-1][i][1]][0] \]

\[sa[k][i][0]=sa[k-1][i][0]+sa[k-1][dp[k-1][i][0]][1] \]

\[sa[k][i][1]=sa[k-1][i][1]+sa[k-1][dp[k-1][i][1]][0] \]

\[sb[k][i][0]=sb[k-1][i][0]+sb[k-1][dp[k-1][i][0]][1] \]

\[sb[k][i][1]=sb[k-1][i][1]+sb[k-1][dp[k-1][i][1]][0] \]

其实就是将最后的从\(i\)\(k-1\)步转移的时候把开车的人变一下

那么我们在处理查询前就可以将所有信息算出来了

for(int k=1;k<=23;k++){
    for(int i=1;i<=n;i++){
        for(int f=0;f<2;f++){
            int qian=dp[k-1][i][f];//这样写着方便一点 不然眼睛容易爆
            if(qian==-1){//如果前一个走不到那这个也走不到  -1在前面的预处理的时候处理出来的
                dp[k][i][f]=-1;
                sa[k][i][f]=0;
                sb[k][i][f]=0;
                continue;
            }
            int nf=f;
            if(k==1){//反转一下
                nf=(nf+1)%2;
            }
            dp[k][i][f]=dp[k-1][qian][nf];
            sa[k][i][f]=sa[k-1][i][f]+sa[k-1][qian][nf];
            sb[k][i][f]=sb[k-1][i][f]+sb[k-1][qian][nf];
        }
    }
}

那么最后怎么求题目所问的东西呢?

1 . 对于限制了的总路程 \(x_0\) 从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 0,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。

2 . 对于任意城市 \(x_i\)和出发城市 \(s_i\) , 小 A 开车行驶的路程总数以及小 B 行驶的路程总数。

我们明显需要对于每一个点进行询问

那么就按照\(k\)从大到小扫 只要扫到可以走到的点就从当前的点再扫 直到没有可以走的点

void lll(int s,int x){
	ssa=0,ssb=0,flag=0;
	for(int k=23;k>=0;k--){
		if(dp[k][s][flag]!=-1&&sa[k][s][flag]+sb[k][s][flag]<=x){//能走到并且路程不超过最大限制
			x-=sa[k][s][flag]+sb[k][s][flag];//最大限制减一下
			ssa+=sa[k][s][flag];
			ssb+=sb[k][s][flag];
			if(k==0){
				flag=(flag+1)%2;//反转
			}
			s=dp[k][s][flag];//又从这个点开始找
		}
	}
	return;
}

第一问 我们直接从\(1\)~\(n\)扫一遍 反正不会超时

第二问 对于每一个查询直接算一遍 也不会超时


最后附上这个诡异的代码

代码

(忽略猎奇变量名和代码风格)

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int h[100005];
int x0;
int m;
int A[100005];
int B[100005];
int sA[100005];
int sB[100005];
int dp[25][100001][2];
int sa[25][100001][2];
int sb[25][100001][2];
int ssa,ssb,flag;
unordered_map <int,int> iid;
void lll(int s,int x){
	ssa=0,ssb=0,flag=0;
	for(int k=23;k>=0;k--){
		if(dp[k][s][flag]!=-1&&sa[k][s][flag]+sb[k][s][flag]<=x){
			x-=sa[k][s][flag]+sb[k][s][flag];
			ssa+=sa[k][s][flag];
			ssb+=sb[k][s][flag];
			if(k==0){
				flag=(flag+1)%2;
			}
			s=dp[k][s][flag];
		}
	}
	return;
}
void ycl(){
	set<int> s;
	A[n]=-1;
	B[n]=-1;
	sA[n]=0;
	sB[n]=0;
	s.insert(h[n]);
	for(int i=n-1;i>=1;i--){
		auto it=s.lower_bound(h[i]);
		int cnt=0;
		int ans[5];
		if(it!=s.end()){
			ans[++cnt]=*it;
			auto lit=next(it,1);
			if(lit!=s.end()){
				ans[++cnt]=*lit;
			}
		}
		if(it!=s.begin()){
			auto lit=prev(it,1);
			ans[++cnt]=*lit;
			if(lit!=s.begin()){
				auto llit=prev(lit,1);
				ans[++cnt]=*llit;
			}
		}
		for(int j=1;j<=cnt;j++){
			ans[j]=iid[ans[j]];
		}
		sort(ans+1,ans+cnt+1,[&](int a,int b){
			if(abs(h[a]-h[i])==abs(h[b]-h[i])){
				return h[a]<h[b];
			}
			return abs(h[a]-h[i])<abs(h[b]-h[i]);
		});
		A[i]=-1,B[i]=-1;
		sA[i]=0,sB[i]=0;
		if(cnt>=1){
			B[i]=ans[1];
			sB[i]=abs(h[B[i]]-h[i]);
		}if(cnt>=2){
			A[i]=ans[2];
			sA[i]=abs(h[A[i]]-h[i]);
		}
		s.insert(h[i]);
	}
	return;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>h[i];
		iid[h[i]]=i;
	}
	ycl();
	for(int i=1;i<=n;i++){
		dp[0][i][0]=A[i];
		dp[0][i][1]=B[i];
		sa[0][i][0]=sA[i];
		sa[0][i][1]=0;
		sb[0][i][1]=sB[i];
		sb[0][i][0]=0;
		if(A[i]==-1){
			sa[0][i][0]=0;
		}
		if(B[i]==-1){
			sb[0][i][1]=0;
		}
	}
	for(int k=1;k<=23;k++){
		for(int i=1;i<=n;i++){
			for(int f=0;f<2;f++){
				int qian=dp[k-1][i][f];
				if(qian==-1){
					dp[k][i][f]=-1;
					sa[k][i][f]=0;
					sb[k][i][f]=0;
					continue;
				}
				int nf=f;
				if(k==1){
					nf=(nf+1)%2;
				}
				dp[k][i][f]=dp[k-1][qian][nf];
				sa[k][i][f]=sa[k-1][i][f]+sa[k-1][qian][nf];
				sb[k][i][f]=sb[k-1][i][f]+sb[k-1][qian][nf];
			}
		}
	}
	cin>>x0;
	double Min=1e18;
	int id=1;
	for(int i=1;i<=n;i++){
		lll(i,x0);
		double lans;
		if(ssb==0){
			lans=1e18;
		}else{
			lans=ssa*1.0/ssb;
		}
		if(lans<Min){
			id=i;
			Min=lans;
		}else if(lans==Min){
			if(h[i]>h[id]){
				id=i;
			}
		}
	}
	cout<<id<<endl;
	cin>>m;
	for(int i=1;i<=m;i++){
		int s,x;
		cin>>s>>x;
		lll(s,x);
		cout<<ssa<<" "<<ssb<<endl;
	}
}
posted @ 2025-12-09 21:44  xwy114514  阅读(3)  评论(0)    收藏  举报