[NOIP2012]开车旅行
题目大意
旅行过程中有个n个城市,从西向东按序号排列。
旅行过程中,小 \(\text{A}\) 和小 \(\text{B}\) 轮流开车,第一天小 \(\text{A}\) 开车,之后每天轮换一次。他们计划选择一个城市 \(s\) 作为起点,一直向东行驶,并且最多行驶 \(x\) 公里就结束旅行。
小 \(\text{A}\) 和小 \(\text{B}\) 的驾驶风格不同,小 \(\text{B}\) 总是沿着前进方向选择一个最近的城市作为目的地,而小 \(\text{A}\) 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出 \(x\) 公里,他们就会结束旅行。
需要回答两个问题。
1、 对于一个给定的 \(x=x_0\),从哪一个城市出发,小 \(\text{A}\) 开车行驶的路程总数与小 \(\text{B}\) 行驶的路程总数的比值最小(如果小 \(\text{B}\) 的行驶路程为 \(0\),此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 \(\text{A}\) 开车行驶的路程总数与小 \(\text{B}\) 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。
2、对任意给定的 \(x=x_i\) 和出发城市 \(s_i\),小 \(\text{A}\) 开车行驶的路程总数以及小 \(\text B\) 行驶的路程总数。
分析
本题是明显的实现不算特别麻烦,但是思路特别难想到。我尽量将完整的思路推导过程叙述清楚。
预处理
首先,我们可以发现一个特点,若是城市已经选定,那接下里对于小A和小B而言,接下来去的点已经确定了。
对于编号为x的点,所求的就是编号x+1~n的城市构成的集合中距离城市x最近和次近的城市
这个操作,我们之前也遇见过很多,可以直接用C++中的set实现,动态的维护关于最大值最小值或某个值附近的值。
这里就不多说了。
其中有几点需要注意。
- 由于查找时会涉及到x周围的四个节点,因此在一开始要先向平衡树分别插入2个极大值和极小值节点,以免越界。其中要注意,极大值极小值设置后,其计算过程中很容易就爆int了,因此一定一定开long long,并且题解中排名第一的虽然没开long long也过了,但是是因为样例太弱了,而且设置的最大值恰好可以过样例,但我们一定要注意。
- 由于在平衡树中每个城市需要保存编号和海拔两个数据,因此在使用set时要重载运算符来保证节点set中的顺序。
- set的迭代器只支持++和--两个操作,不能使用其它运算。
struct City
{
int id;
LL al;//identifier,altitude
friend bool operator < (City a,City b)
{
return a.al<b.al;
}
};
multiset<City> q;
void pre()
{
h[0]=INF,h[n+1]=-INF;
City 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;
City now;
now.id = i,now.al = h[i];
q.insert(now);
auto p = q.lower_bound(now);
p--;
LL lt = (*p).id,lh = (*p).al;
p++,p++;
LL ne = (*p).id,nh = (*p).al;
p--;
if(abs(nh-h[i])>=abs(h[i]-lh))
{
gb = lt;
p--;p--;
if(abs(nh-h[i])>=abs(h[i]-(*p).al)) ga = (*p).id;
else ga = ne;
}
else
{
gb = ne;
p++,p++;
if(abs(h[i]-lh)<=abs((*p).al-h[i])) ga = lt;
else ga = (*p).id;
}
}
}
倍增优化
接下来,我们预处理完后,知道了所有点分别由小A和小B驾驶时会去到的城市。
那我们重新来看一下问题,现在的问题变成了。
- 我们在只能走x路程的情况下,我们从哪个城市开始,能使得小A走的路程与小B走的路程的比值最小
- 我们只能走x路程的情况,且起点固定的情况下,小A和小B分别走的路程
我们发现,第一个问题我们可以遍历所有的点,这样就变成了第二个问题。
则我们现在只用解决第二个问题即可。
我们只能走x路程的情况,且起点固定的情况下,小A和小B分别走的路程
这里用到的倍增思想是非常常用的优化。LCA,ST表与多重背包等都用了这样的思路优化。
我们可以发现,这与求LCA的时候非常像。
每个点走固定步数能到的终点和能走的距离都知道了,那我们可以预处理出来,每个点走\(2^i\)的步数可以到的终点与距离。这样我们就可以用二进制拼凑的思想,去得到最接近x的答案。
这里推荐一个例题。P1613 跑路
好啦,现在我们知道我们要计算什么啦。
- \(f_{i,j,k}表示从城市j出发,行驶2^i天,k先开车,最终会到达的城市\)
- \(da_{i,j,k}表示从城市j出发,行驶2^i天,k先开车,小A行驶的路程长度\)
- \(db_{i,j,k}表示从城市j出发,行驶2^i天,k先开车,小B行驶的路程长度\)
\(其中k=0表示小A先开车,k=1表示小B先开车\)
那我们的预处理推导也比较好想啦,其中需要注意的一个是。从\(2^0转移到2^1时,前后两半开车的人会发生变化\)。
初值
- \(f_{0,j,0}=ga_j,f_{0,j,1}=gb_j\)
- \(da_{0,j,0}=dist_{j,ga_j},da_{0,j,1}=0\)
- \(db_{0,j,0}=0,db_{0,j,1}=dist_{j,gb_j}\)
for(int i=n;i;i--)
{
//......预处理
f[0][i][0] = ga,f[0][i][1] = gb;
da[0][i][0] = abs(h[i] - h[ga]);
db[0][i][1] = abs(h[i] - h[gb]);
}
递推式子
当i=1时:
- \(f_{1,j,k}=f_{0,f_{0,j,k},1-k}\)
- \(da_{1,j,k}=da_{0,j,k}+da_{0,f_{0,j,k},1-k}\)
- \(db_{1,j,k}=db_{0,j,k}+db_{0,f_{0,j,k},1-k}\)
当i>1时:
- $ f_{i,j,k}=f_{i-1,f_{i-1,j,k},k}$
- \(da_{i,j,k}=da_{i-1,j,k}+da_{i-1,f_{i-1,j,k},k}\)
- \(db_{i,j,k}=db_{i-1,j,k}+db_{i-1,f_{i-1,j,k},k}\)
for(int i=1;i<=18;i++)
for(int j=1;j<=n;j++)
for(int k=0;k<2;k++){
if(i==1)
{
auto anc = f[0][j][k];
f[1][j][k] = f[0][anc][1-k];
da[1][j][k] = da[0][j][k] + da[0][anc][1-k];
db[1][j][k] = db[0][j][k] + db[0][anc][1-k];
}
else
{
auto anc = f[i-1][j][k];
f[i][j][k] = f[i-1][anc][k];
da[i][j][k] = da[i-1][j][k] + da[i-1][anc][k];
db[i][j][k] = db[i-1][j][k] + db[i-1][anc][k];
}
}
解决问题1
刚刚,我们就说了,解决问题1就是遍历每个点,当成问题2解决。
int la,lb,ansid;
void calc(int S,int X)
{
int p=S;
la=0,lb=0;//初始化
for(int i=18;i>=0;i--)
if(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];
}//倍增模拟前进
}
for(int i=1;i<=n;i++)
{
calc(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;//比较求解
}
cout<<ansid<<endl;
解决问题2
就是对于每个给定的\(x_i,s_i\),用一次\(calc(s_i,x_i)\)
Ac_code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
const LL INF = 5e9;
struct City
{
int id;
LL al;//identifier,altitude
friend bool operator < (City a,City b)
{
return a.al<b.al;
}
};
int n,m,x0,la,lb,ansid;
int s[N],x[N];
int f[20][N][2];
LL da[20][N][2],db[20][N][2],h[N];
double ans = INF*1.0;
multiset<City> q;
void calc(int S,int X)
{
int p = S;
la = 0,lb = 0;
for(int i=18;i>=0;i--){
if(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];
}
}
}
void pre()
{
h[0]=INF,h[n+1]=-INF;
City 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;
City now;
now.id = i,now.al = h[i];
q.insert(now);
auto p = q.lower_bound(now);
p--;
LL lt = (*p).id,lh = (*p).al;
p++,p++;
LL ne = (*p).id,nh = (*p).al;
p--;
if(abs(nh-h[i])>=abs(h[i]-lh))
{
gb = lt;
p--;p--;
if(abs(nh-h[i])>=abs(h[i]-(*p).al)) ga = (*p).id;
else ga = ne;
}
else
{
gb = ne;
p++,p++;
if(abs(h[i]-lh)<=abs((*p).al-h[i])) ga = lt;
else ga = (*p).id;
}
f[0][i][0] = ga,f[0][i][1] = gb;
da[0][i][0] = abs(h[i] - h[ga]);
db[0][i][1] = abs(h[i] - h[gb]);
}
for(int i=1;i<=18;i++)
for(int j=1;j<=n;j++)
for(int k=0;k<2;k++){
if(i==1)
{
auto anc = f[0][j][k];
f[1][j][k] = f[0][anc][1-k];
da[1][j][k] = da[0][j][k] + da[0][anc][1-k];
db[1][j][k] = db[0][j][k] + db[0][anc][1-k];
}
else
{
auto anc = f[i-1][j][k];
f[i][j][k] = f[i-1][anc][k];
da[i][j][k] = da[i-1][j][k] + da[i-1][anc][k];
db[i][j][k] = db[i-1][j][k] + db[i-1][anc][k];
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",h+i);
scanf("%d%d",&x0,&m);
for(int i=1;i<=m;i++) scanf("%d%d",s+i,x+i);
pre();
for(int i=1;i<=n;i++)
{
calc(i,x0);
double nans = (double)la/(double)lb;
if(nans<ans)
{
ans = nans;
ansid = i;
}
else if(nans==ans&&h[ansid]<h[i])
ansid = i;
}
printf("%d\n",ansid);
for(int i=1;i<=m;i++)
{
calc(s[i],x[i]);
printf("%d %d\n",la,lb);
}
return 0;
}

浙公网安备 33010602011771号