• 《算法竞赛进阶指南》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;
    }
  • 相关阅读:
    Java文件之NIO核心组件之三选择器
    plsql备份表---只是表---不包含表数据
    根据id来大量删除数据between
    符号的问题
    excel表格中添加单引号的方法
    oracle中insert 多条数据方法
    sql developer以字段来删除大量数据
    Day 29
    Day 28
    Day 27
  • 原文地址:https://www.cnblogs.com/randy-lo/p/13415865.html
Copyright © 2020-2023  润新知