【Ybtoj 第18章例2】开车旅行【倍增问题】

9 篇文章 0 订阅
这篇博客探讨了一种利用双向链表和动态规划解决城市驾驶问题的方法。首先,通过对城市按顺序排序,确定最小点和次小点。接着,建立动态规划状态转移方程,通过倍增技巧优化计算过程。最后,给出了代码实现来计算最优路径和小A与小B行驶路程的比值。此问题涉及数据结构、算法和最优化策略。
摘要由CSDN通过智能技术生成

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


解题思路

预处理最小点和次小点:

我们用双向链表。l[i]表示在原序列中第i个点排序后左边的点,r[i]表示在原序列中第i个点排序后右边的点。
先不管方向,将所有城市排序之后,它的最小和次小点一定在 l [ i ] , l [ l [ i ] ] , r [ i ] , r [ r [ i ] ] l[i],l[l[i]],r[i],r[r[i]] l[i],l[l[i]],r[i],r[r[i]]的位置,所以说如果排序我们的处理会方便很多。

首先我们记下每个城市排序之后的位置,然后从第一个城市开始向 l [ i ] , l [ l [ i ] ] , r [ i ] , r [ r [ i ] ] l[i],l[l[i]],r[i],r[r[i]] l[i],l[l[i]],r[i],r[r[i]]处找点。很显然,由于是第一个点,这个时候找到的任何点一定在它东边。同样的,找完第一个点后将第一个点删除,那么第二个点自然成为了第一个点,同理。


DP:
不难发现,每个点出发,先由小A/小B开车,开若干次车。一定是有一个唯一的终点,而且最多开n次车旅行就结束了。所以我们可以考虑倍增,令 f 0 / 1 , i , j f_{0/1,i,j} f0/1,i,j表示从城市i出发,由小A/小B先开车,开2^j次车到达的终点, d i s 0 / 1 , i , j dis_{0/1,i,j} dis0/1,i,j表示从城市 i i i出发,由小A先开车,开 2 j 2^j 2j车,小A/小B开车的路程。

(因为 2 0 = 1 2^0=1 20=1是奇数,因此在转移到 2 2 2^2 22 时,前后两半时间先开车的人不一样,因此要独立出来转移;其余的状态转移方程均相同。

转移方程:

  • f 0 , i , j = f 0 , f 0 , i , j − 1 , j − 1 f_{0,i,j}=f_{0,f_{0,i,j-1},j-1} f0,i,j=f0,f0,i,j1,j1
  • d i s 0 , i , j = d i s 0 , i , j − 1 + d i s 0 , f 0 , i , j − 1 , j − 1 dis_{0,i,j}=dis_{0,i,j-1}+dis_{0,f_{0,i,j-1},j-1} dis0,i,j=dis0,i,j1+dis0,f0,i,j1,j1
  • d i s 1 , i , j = d i s 1 , i , j − 1 + d i s 1 , f 0 , i , j − 1 , j − 1 dis_{1,i,j}=dis_{1,i,j-1}+dis_{1,f_{0,i,j-1},j-1} dis1,i,j=dis1,i,j1+dis1,f0,i,j1,j1

特别的,当 j = 1 j=1 j=1有:

  • f 0 , i , 1 = f 1 , f 0 , i , 0 , 0 f_{0,i,1}=f_{1,f_{0,i,0},0} f0,i,1=f1,f0,i,0,0
  • d i s 0 , i , 1 = d i s 0 , i , 0 dis_{0,i,1}=dis_{0,i,0} dis0,i,1=dis0,i,0
  • d i s 1 , i , 1 = d i s 1 , f 0 , i , 0 , 0 dis_{1,i,1}=dis_{1,f_{0,i,0},0} dis1,i,1=dis1,f0,i,0,0

PS:倍增的初始化要注意的的地方。

对于f,记得一定是2^j个两步之后所到达的点,与A,B谁开车无关。
对于B,由于开始一定是A先跑,所以我们要用次小点的最小点之间的距离初始化。


求解问题1:

很显然,要知道小A和小B行驶路程的比值,只要知道他们分别行驶的路程就可以了。这里定义一个函数 f i n d ( S , X ) find(S,X) find(S,X)求解从城市S出发,最多行驶X公里,小A和小B分别行驶了多少距离。
通过上面DP推出的三个数组,函数的实现就不难了,只要倍增模拟前进即可。
求解问题2就更明显了,直接多次求解 f i n d ( s i , x i ) find(s_i,x_i) find(si,xi)即可,这里就不再赘述。


代码
调了超级九。。。枯了。。。

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;

const long long INF=0x7ffffffffffff;
int n,s0,m,s[100010],l[100010],r[100010],f[2][100010][20];
long long dis[2][100010][20],w[100010],la,lb,x1,x[100010];
double c=INF;

struct cc {
	long long x;
	int id;
} h[100010];

bool cmp(cc l,cc r) {
	return l.x<r.x;
}

void find(long long s,long long x1) {
	long long s1=s;
	la=0,lb=0;
	for(int j=18; j>=0; j--) {
		if(f[0][s1][j]&&x1>=dis[0][s1][j]+dis[1][s1][j]) {
			la+=dis[0][s1][j],lb+=dis[1][s1][j];
			x1-=dis[0][s1][j]+dis[1][s1][j];
			s1=f[0][s1][j];
		}
	}
	return;
}

int main() {
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%lld",&w[i]);
		h[i].x=w[i];
		h[i].id=i;
	}
	scanf("%lld%d",&x1,&m);
	for(int i=1; i<=m; i++)
		scanf("%d%lld",&s[i],&x[i]);
	sort(h+1,h+n+1,cmp);
	for(int i=1; i<=n; i++) {
		l[h[i].id]=h[i-1].id;
		r[h[i].id]=h[i+1].id;
	}
	for(int i=1; i<=n; i++) {
		long long a=INF,b=INF,st[5];
		int aa=0,bb=0;
		
		if(l[i])st[1]=w[i]-w[l[i]];
			else st[1]=INF;
		if(r[i])st[2]=w[r[i]]-w[i];
			else st[2]=INF;
		if(l[l[i]])st[3]=w[i]-w[l[l[i]]];
			else st[3]=INF;
		if(r[r[i]])st[4]=w[r[r[i]]]-w[i];
			else st[4]=INF;
		for(int j=1; j<=2; j++)
			if(st[j]<a)
				a=st[j],aa=j;
		if(aa==1)f[1][i][0]=l[i];
		if(aa==2)f[1][i][0]=r[i];
		for(int j=1; j<=4; j++)
			if(j!=aa&&st[j]<b)
				b=st[j],bb=j;
		if(bb==1)f[0][i][0]=l[i];
		if(bb==2)f[0][i][0]=r[i];
		if(bb==3)f[0][i][0]=l[l[i]];
		if(bb==4)f[0][i][0]=r[r[i]];
		if(f[0][i][0])dis[0][i][0]=b;
		if(f[1][i][0])dis[1][i][0]=a;
		if(l[i])r[l[i]]=r[i];
		if(r[i])l[r[i]]=l[i];
	}
	for(int i=1; i<=n; i++) {
		f[0][i][1]=f[1][f[0][i][0]][0];
		dis[0][i][1]=dis[0][i][0];
		dis[1][i][1]=dis[1][f[0][i][0]][0];
	}
	for(int i=1; i<=n; i++)
		dis[1][i][0]=0;
	for(int j=2; j<=18; j++) {
		for(int i=1; i<=n; i++) {
			f[0][i][j]=f[0][f[0][i][j-1]][j-1];
			dis[0][i][j]=dis[0][i][j-1]+dis[0][f[0][i][j-1]][j-1];
			dis[1][i][j]=dis[1][i][j-1]+dis[1][f[0][i][j-1]][j-1];
		}
	}
	for(int i=1; i<=n; i++) {
		find(i,x1);
		double cc=(double)la/(double)lb;
		if(cc<c) {
			c=cc;
			s0=i;
		} else if(cc==c&&w[i]>w[s0])
			s0=i;
	}

	printf("%lld\n",s0);
	for(int i=1; i<=m; i++) {
		find(s[i],x[i]);
		printf("%lld %lld\n",la,lb);
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值