[题解] NOIP2012 开车旅行

例题 NOIP2012 开车旅行

传送门

题目大意(做题即翻译)

给你一个已知的有向图,每个结点有一个编号即高度,从西到东排列且只能从西到东走,任意两点间的距离为他俩点的高度差的绝对值。

现在有两个人要轮流开车,任选一个起点 S 出发,A 开一天车,B 开一天车。不同的是,A 都会开往第二近的地方,B 都会开往最近的地方。

问你两个问题:给出你最大行驶距离 x0 ,从哪个点开始走 la / lb 最小(la,lb是他俩开车分别走的距离),第二个问题和第一个类似,求出 la ,lb 即可。

分析

不难发现,处理出每个点的最近城市和次近城市是有必要的,可以用平衡树来维护:

  • 一个点的最近城市无非就是他在平衡树里的前驱或后继

  • 次近城市有两种情况:

  • 1.最近城市是前驱结点,那么次近城市就是前驱的前驱和后继之一。

  • 2.最近城市是后继结点,那么次近城市就是前驱和后继的后继之一。

这里有两个小细节:

1. 由于只能从小往大开,因此要从 n 到 1倒序遍历更新。

2. 在处理 n 的时候可能越界,需要插入两个最大值和两个最小值:h[ 0 ]=INF , h[ n + 1 ] = -INF(想一想,为什么)

因此维护功能就写出来了

h[0]=INF,h[n+1]=-INF;
	node st;
	st.id=0,st.al=INF;
	q.insert(st),q.insert(st);
	st.id=n+1,st.al=-INF;
	q.insert(st),q.insert(st);
	for(int i=n;i;i--){
		int ga,gb;
		node now;
		now.id=i,now.al=h[i];
		q.insert(now);
		set<node>::iterator p=q.lower_bound(now);
		p--;
		int lt=p->id,lh=p->al;//前驱 
		p++,p++;
		int nt=p->id,nh=p->al;//后继
		p--;//回到初始位置
		if(abs(lh-h[i])<=abs(nh-h[i])){//前驱最近 
			gb=lt;
			p--,p--;
			if(abs(p->al-h[i])<=abs(nh-h[i])){//前驱的前驱 
				ga=p->id;
			}
			else ga=nt;
		}
		else{//后继最近 
			gb=nt;
			p++,p++;
			if(abs(p->al-h[i])>=abs(lh-h[i])){
				ga=lt;
			}
			else ga=p->id;
		}
   	}

这里用到了set维护最大和次大值的思想,非常重要。

回到问题上,得到了每个点的最近和次近结点,肯定是要记录的,偷偷看一眼标签,是倍增!因此需要用一个 f(i , j , k)k 先开车存储从 i 号结点走 2^j 次到达的城市结点,得到如下的初始化。同理,采用倍增思想,da db分别表示 A 和 B 的开车距离

	f[i][0][0]=ga,f[i][0][1]=gb;
	da[i][0][0]=abs(h[ga]-h[i]);
	db[i][0][1]=abs(h[gb]-h[i]);

怎么转移?又一个细节,i = 1时,前半段开车和后半段开车的不是一个人:

if(j==1){
	f[i][1][k]=f[i][0][k]+f[f[i][0][k]][0][1-k];
	da[i][1][k]=da[i][0][k]+da[f[i][0][k]][0][1-k];
	db[i][1][k]=db[i][0][k]+db[f[i][0][k]][0][1-k];
}
else if(j>1){
	f[i][j][k]=f[i][j-1][k]+f[f[i][j-1][k]][j-1][k];
	da[i][j][k]=da[i][j-1][k]+da[f[i][j-1][k]][j-1][k];
	db[i][j][k]=db[i][j-1][k]+db[f[i][j-1][k]][j-1][k];
}

因此问题解决了一大半。

怎么模拟行进过程?与倍增的查询是类似的。

void work1(int S,int X){
	int p=S;
	la=0;lb=0;
	for(int i=18;i>=0;i--){//a先开车 
		if(f[p][i][0]&&la+lb+da[p][i][0]+db[p][i][0]<=X){//不能越界 
			la+=da[p][i][0];
			lb+=db[p][i][0];
			p=f[p][i][0];
		}
	}
}

问题一和二的本质是一样的,不知道你发现了没有?

完整代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <set>
#include <cstdlib>
using namespace std;
const int maxn=1e5+200,INF=2e9;
struct node{
	int id,al;
	bool operator <(const node &x)const{
		return al<x.al;//按海拔来 
	}
};
int n,m,x0,la,lb;
int h[maxn],s[maxn],x[maxn];
int f[maxn][20][3],da[maxn][20][3],db[maxn][20][3];
double ans=INF*1.0;//控制精度
multiset <node> q;
void prework(){
	h[0]=INF,h[n+1]=-INF;
	node st;
	st.id=0,st.al=INF;
	q.insert(st),q.insert(st);
	st.id=n+1,st.al=-INF;
	q.insert(st),q.insert(st);
	for(int i=n;i;i--){
		int ga,gb;
		node now;
		now.id=i,now.al=h[i];
		q.insert(now);
		set<node>::iterator p=q.lower_bound(now);
		p--;
		int lt=p->id,lh=p->al;//前驱 
		p++,p++;
		int nt=p->id,nh=p->al;//后继
		p--;//回到初始位置
		if(abs(lh-h[i])<=abs(nh-h[i])){//前驱最近 
			gb=lt;
			p--,p--;
			if(abs(p->al-h[i])<=abs(nh-h[i])){//前驱的前驱 
				ga=p->id;
			}
			else ga=nt;
		}
		else{//后继最近 
			gb=nt;
			p++,p++;
			if(abs(p->al-h[i])>=abs(lh-h[i])){
				ga=lt;
			}
			else ga=p->id;
		}
		f[i][0][0]=ga,f[i][0][1]=gb;
		da[i][0][0]=abs(h[ga]-h[i]);
		db[i][0][1]=abs(h[gb]-h[i]);
	}
	for(int i=1;i<=18;i++)
		for(int j=1;j<=n;j++)
			for(int k=0;k<2;k++){
				if(i==1){
					f[j][1][k]=f[f[j][0][k]][0][1-k];
					da[j][1][k]=da[j][0][k]+da[f[j][0][k]][0][1-k];
					db[j][1][k]=db[j][0][k]+db[f[j][0][k]][0][1-k];
				}
				else{
					f[j][i][k]=f[f[j][i-1][k]][i-1][k];
					da[j][i][k]=da[j][i-1][k]+da[f[j][i-1][k]][i-1][k];
					db[j][i][k]=db[j][i-1][k]+db[f[j][i-1][k]][i-1][k];			
				}
			}
}
void work1(int S,int X){
	int p=S;
	la=0;lb=0;
	for(int i=18;i>=0;i--){//a先开车 
		if(f[p][i][0]&&la+lb+da[p][i][0]+db[p][i][0]<=X){//不能越界 
			la+=da[p][i][0];
			lb+=db[p][i][0];
			p=f[p][i][0];
		}
	}
}
int ansid;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",h+i);
	scanf("%d%d",&x0,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",s+i,x+i);
	prework();
	//for(int i=1;i<=n;i++)printf("%d %d %d\n",f[i][0][0],da[i][0][0],db[i][0][1]);
	//system("pause");
	for(int i=1;i<=n;i++){
		work1(i,x0);
		double nowans=(double)la/(double)lb;
		if(nowans<ans){
			ans=nowans;
			ansid=i;
		}
		else if(nowans==ans&&h[ansid]<h[i]){
			ansid=i;
		}
	}
	printf("%d\n",ansid);
	for(int i=1;i<=m;i++){
		work1(s[i],x[i]);
		printf("%d %d\n",la,lb);
	}
	return 0;
}
posted @ 2021-08-12 16:30  ¶凉笙  阅读(42)  评论(0编辑  收藏  举报