《算法竞赛进阶指南》0x57倍增优化DP AcWing293 开车旅行


	《算法竞赛进阶指南》0x57倍增优化DP  AcWing293 开车旅行
[编程语言教程]

题目链接:https://www.acwing.com/problem/content/295/

题目给定n个城市,在一个方向上有序排列,每个城市有高度,有两个人a,b,定义两个城市之间的距离是高度之差的绝对值。b只会选择右边距离他最小的一个作为下一个点,a只会选择右边次小的点作为下一个点。a先走。问题一:给出一个最大距离X0,问从哪一个点开始走,la/lb最大?问题二:给出一系列起点S和距离X,求a,b走的距离。

本问题中,只要起点S和距离X确定了那么路线就一定唯一。首先,下一个点的产生可以通过“邻值查找”相同的算法利用链表或者是set进行处理,时间复杂度是O(nlogn),这里要同时查找最小值和次小值,故可以扫描最近的可能的四个点,找到最小的和次小的。获得ga和gb数组(下一个点的位置)之后就可以初始化f数组,表示的是k=0/1从j位置开始,走了1<<i步之后的位置,通过dp进行转移即可,注意dp的边界,i为0的时候,走一步的情况只需要用gagb数组更新即可,i为1的时候需要两个人来移动。下面就是更新da数组db数组表示a,b走过的距离。分别如f进行递推计算即可。最终得到f数组。

如果计算从S开始,最远移动距离是X,可以将步数按照二进制位进行划分,最多不超过logn次计算,通过da数组和db数组即可计算出a、b移动的距离,这里由于是a先走,而且步数一直是偶数,所以一直都是从a开始走,最后一个可能步数是奇数,但是也是从a开始走的。

问题一直接枚举每个起点即可,时间复杂度是O(nlogn),问题二直接计算calc函数,时间复杂度是O(mlogn)

注意:如果距离相同的话取海拔低的,第一种问题中最终如果两个比值相同的话就取海拔最高的。

代码:

#include<iostream>
#include<cstdio>
#include<set>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 100010,M=17; 
typedef long long ll;
typedef pair<ll,int> PLI;
const ll inf=1e13;
int n;
int h[maxn];
int f[M][maxn][2];
int ga[maxn],gb[maxn];
ll da[M][maxn][2],db[M][maxn][2];

void init_g(){//由于高度是不一样的所以直接用set求邻值即可 
    set<PLI> S;
    S.insert({inf,0});//所有不能到的位置都是0 
    S.insert({inf+1,0});
    S.insert({-inf,0});
    S.insert({-inf-1,0});//防止越界,插入四个无穷大 
     
    for(int i=n;i;i--){//从i之后的中寻找邻值 
        PLI t={h[i],i};
        set<PLI>::iterator j=S.lower_bound(t);
        j++;//找到其上第二个,往下搜索四个值的最小值以及次小值
        vector<PLI> cand;
        for(int k=0;k<4;k++){
            cand.push_back(*j);
            j--;
        } 
        ll d1=inf,d2=inf;//最小值以及次小值 
        int p1=0,p2=0;
        for(int k=3;k>=0;k--){//如果距离相等的话就选择海拔较低的点,所以从下往上看 
            ll d=abs(h[i]-cand[k].first);
            if(d<d1){//次小与最小的更新 ,注意赋值的先后顺序 
                d2=d1,p2=p1;
                d1=d,p1=cand[k].second; 
            }else if(d<d2){
                d2=d,p2=cand[k].second;
            }
        }
        ga[i]=p2,gb[i]=p1;//最小次小都以及找到 
        
        S.insert(t);
    }
}

void init_f(){
    for(int i=0;i<M;i++){//阶段 
        for(int j=1;j<=n;j++){//出发点 
            if(!i)f[0][j][0]=ga[j],f[0][j][1]=gb[j];
            else{
                for(int k=0;k<2;k++){
                    if(i==1)f[i][j][k]=f[0][f[0][j][k]][1-k];
                    else f[i][j][k]=f[i-1][f[i-1][j][k]][k];
                }
            }
        } 
    }
}

int get_dist(int i,int j){
    return abs(h[i]-h[j]);
}

void init_d(){
    for(int i=0;i<M;i++){
        for(int j=1;j<=n;j++){
            if(!i){
                da[0][j][0]=get_dist(j,ga[j]);
                da[0][j][1]=0;
                db[0][j][1]=get_dist(j,gb[j]);
                db[0][j][0]=0;
            }else{
                for(int k=0;k<2;k++){
                    if(i==1){
                        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];
                    }else{
                        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];
                    }
                }
            }
        }
    }
}

//从p开始,最远距离是x,a走过的距离,b走过的距离 
void calc(int p,int x,int& la,int& lb){//保存a,b走的路的长度信息 
    la=lb=0;
//    倍增,将步数按照二进制从高到低进行枚举 
//  走过的都是偶数步,一定是从a开始走的,只有最后一步是1,也是a走的 
    for(int i=M-1;i>=0;i--){//倒序判断长度是否可走,将步数表示成二进制 
        if(f[i][p][0] && la+da[i][p][0]+lb+db[i][p][0] <= x){
            la+=da[i][p][0];
            lb+=db[i][p][0];
            p=f[i][p][0];//下一个位置 
        } 
    }
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++)scanf("%d",&h[i]);
    
    init_g();
    init_f();
    init_d();
    
    int p,x;
    scanf("%d",&x);
    int res=0,max_h=0;
    double min_ratio=inf;
    for(int i=1;i<=n;i++){
        int la,lb;
        calc(i,x,la,lb);
        double ratio=lb ? (double)la/lb : inf;
         if(ratio<min_ratio || ratio==min_ratio && h[i]>max_h){//ratio相等的时候输出海拔最大的城市 
             min_ratio=ratio;
             max_h=h[i];
             res=i;
        }
    }    
    
    cout<<res<<endl;
    
    int m;
    cin>>m;
    while(m--){
        scanf("%d%d",&p,&x);
        int la,lb;
        calc(p,x,la,lb);
        printf("%d %d
",la,lb);
    }
    
    return 0;
}
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 《算法竞赛进阶指南》0x57倍增优化DP AcWing293 开车旅行