《算法竞赛进阶指南》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; }