[题解] 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;
}