[NOIP 2012 提高组] 开车旅行
前言
作为一个初二的蒟蒻 受教练之令 写的这个题目的做法
题目
[NOIP 2012 提高组] 开车旅行
题意
按照题目给定的行驶规律(小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<j≤n),使得\(∣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就可以用一个三维数组定义出来了
表示 在在第\(i\)个城市开始 已经行驶了\(2^k\) 天 最开始是谁开车(0是A 1是B)
走到的城市
而sa和sb数组同上面的定义 只不过存进去的是A行驶的路程和B行驶的路程
那么我们的初始化应该怎么初始化呢??
首先是dp数组 不难发现
(\(2^0=1\),开一天就是刚刚与处理过后的A,B数组)
而这里的sA,sB数组
(\(2^0=1\),开一天就是刚刚与处理过后的sA,sB数组)
那么接下来就是愉快的状态转移
先看\(k!=1\)的情况
我们发现\(2^k\)次和\(2^{k-1}\)次中间相差\(2^{k-1}\)次只要\(k!=1\) 就相差偶数次
上一次是谁开车这一次就是谁开车
那么我们的转移就非常简单了
从\(i\)走\(k\)步等于从\(i\)走\(k-1\)步的地方再走\(k-1\)步
那么同理
那么下一个\(k=1\)咋办呢 反一下就行了
其实就是将最后的从\(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;
}
}

浙公网安备 33010602011771号