【倍增优化dp】洛谷P1081 开车旅行

版权声明:虽然本蒟蒻很菜,但各位dalao转载请注明出处谢谢。 https://blog.csdn.net/xuxiayang/article/details/88901165

D e s c r p t i o n Descrption

https://www.luogu.org/problemnew/show/P1081
给定 n n 个城市以及它们的海拔高度 H i H_i ,定义两个城市之间的距离为 d i s t ( i , j ) = H i H j ( i < j ) dist(i,j)=|H_i-H_j|(i<j) 只能从编号小的城市走向编号大的城市

现在有两个人小 A A 和小 B B ,它们有一种特殊的癖好

  1. A A 总是走向距离当前城市第二近的城市
  2. B B 总是走向距离当前城市第一近的城市

假设第一天是小 A A 开车,之后小 A A 和小 B B 交错开车

A A 或小 B B 在以下两种情况会停下来

  1. 当它们行驶的路程超过一个给定的值 X X
  2. 它们已经到达第 n n 个城市

第一问:给定 X X ,要求找到一个出发城市,使得从这个城市出发后小A行驶的路程:小B行驶的路程最小,找出这个城市

第二问:给定 m m 组询问,每次询问给定一个出发城市 S S X X ,求出从S出发,行驶总路程不超过X时,小A和小B分别的行驶距离

数据范围:
对于30%的数据,有 1 N 20 , 1 M 20 1≤N≤20,1≤M≤20
对于40%的数据,有 1 N 100 , 1 M 100 1≤N≤100,1≤M≤100
对于50%的数据,有 1 N 100 , 1 M 1 , 000 1≤N≤100,1≤M≤1,000
对于70%的数据,有 1 N 1 , 000 , 1 M 10 , 000 1≤N≤1,000,1≤M≤10,000
对于100%的数据,有 1 N 100 , 000 , 1 M 100 , 000 1≤N≤100,000,1≤M≤100,000

对于100%的数据,有 1 0 9 H i 1 0 9 , 0 X 1 0 9 , 0 S n -10^9\leq H_i\leq10^9,0\leq X\leq 10^9,0\leq S\leq n


S o l u t i o n Solution

首先我们首先要知道小 A A 和小 B B 从第 i i 个城市出发会去往的下一个城市
分别设为 g a ( i ) ga(i) g b ( i ) gb(i)

两种求法:(代码用的是链表的方法)

  1. 建一棵平衡树,依次插入每个 H i H_i ,查询它的前驱和后继即可
  2. 用链表的方法,保存每排序后的数原来对应的位置,因为排了序,所以我们也可以用类似的方法查询最小值和次小值

这两种求法的复杂度都是 O ( n l o g n ) O(nlogn) ,其中第二种方法的瓶颈在于排序

接下来我们考虑求解本题的关键信息:如果我能够知道从某个城市出发 i i 天会到哪个城市,并且小 A A 和小 B B 此时行驶的距离就能求解本题了

由于我们处理出了 g a ( i ) ga(i) g b ( i ) gb(i) ,所以可以直接 d p dp

但。。。这样我们 d p dp 的时空复杂度都是 O ( n 2 ) O(n^2) 的!只能通过70%的数据

实际上,我们可以只求出从某个城市出发 2 i 2^i 天会到哪个城市以及此时两人的行驶距离即可,因为所有的正整数都可以被分解为2整数幂次和

这样我们就可以 O ( n l o g n ) O(nlogn) 进行 d p dp

方程?

f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] 表示行驶了 2 i 2^i 天,当前在第 j j 个城市,0表示小 A A 开,1表示小 B B

初始化: f [ 0 ] [ i ] [ 0 ] = g a ( i ) , f [ 0 ] [ i ] [ 1 ] = g b ( i ) f[0][i][0]=ga(i),f[0][i][1]=gb(i)
o o 表示上一次是谁开车,容易发现,只有 i = = 0 i==0 的时候, o = 1 k o=1-k ,否则 o = k o=k

(因为2的正整数次幂都是偶数,只有2的0次幂是奇数)

动态转移: f [ i ] [ j ] [ k ] = f [ i 1 ] [ f [ i 1 ] [ j ] [ k ] ] [ o ] f[i][j][k]=f[i-1][f[i-1][j][k]][o]

到这儿我们已经愉快了从哪出发开任意天( 2 i 2^i 可以组合成任何正整数天)的城市是哪,我们也可以用同样的方法处理出距离

d a / b [ i ] [ j ] [ 0 / 1 ] d_{a/b}[i][j][0/1] 表示从 j j 城市出发,行驶了 2 i 2^i 天时,且谁在开车时,小 A A 和小 B B 走的距离

初始化: d a [ 0 ] [ i ] [ 0 ] = d i s t ( i , g a ( i ) ) , d b [ 0 ] [ i ] [ 1 ] = d i s t ( i , g b ( i ) ) da[0][i][0]=dist(i,ga(i)),db[0][i][1]=dist(i,gb(i))

动态转移: d a / b [ i ] [ j ] [ k ] = d a / b [ i 1 ] [ j ] [ k ] + d a / b [ i 1 ] [ f [ i 1 ] [ j ] [ k ] ] [ o ] d_{a/b}[i][j][k]=d_{a/b}[i-1][j][k]+d_{a/b}[i-1][f[i-1][j][k]][o]
o o 的意义同上,这里不多赘述

到了这里问题已经成功解决了大部分了,现在考虑如何计算距离不超过 X X 时小 A A 和小 B B 分别行驶的距离

拼凑法!

倒序循环 i i ,试着用行驶 2 i 2^i 天时的行驶距离拼凑,能拼就拼,因为边权非负,所以 d a da d b db 必然单调递减,所以我们拼凑出的值必然是最优的

然后就 w o r k   o u t work\ out

时间复杂度? O ( ( n + m ) l o g n ) O((n+m)logn)


C o d e Code

#include<set>
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;int n,m,x0,x,s,ga[100001],gb[100001],j,l,r,pos[100001],f[21][100001][2],ans;
long long da[21][100001][2],db[21][100001][2],la,lb;
double minn=1e17;
bool o;
struct node{int i,l,r;long long h;}p[100001];
inline bool cmp(node x,node y){return x.h<y.h;}
inline bool zuo()//判断是否左边的更优
{
	if(!l) return 0;
	if(!r) return 1;
	return p[j].h-p[l].h<=p[r].h-p[j].h;
}
inline int nearly(int a,int b)//比较a,b谁离j更近,返回更近的那个的原序号
{
	if(!a) return p[b].i;
	if(!b) return p[a].i;
	if(p[j].h-p[a].h<=p[b].h-p[j].h) return p[a].i;
	return p[b].i;
}
inline long long dist(int a,int b)//a和b对应的距离
{
	return abs(p[pos[a]].h-p[pos[b]].h);//因为是排了序的,所以要用pos
}
inline void solve(long long x,int st)//拼凑
{
	la=lb=0;o=0;
	for(register int i=20;i>=0;i--)
	if(f[i][st][o]&&la+lb+da[i][st][o]+db[i][st][o]<=x)//若还没走完且距离满足
	{
		la+=da[i][st][o];lb+=db[i][st][o];if(i==0) o^=1;st=f[i][st][o];//将其拼上
	}
	return;
}
inline long long read()
{
	char c;int d=1;long long f=0;
	while(c=getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
inline void write(long long x){if(x>9)write(x/10);putchar(x%10+48);return;}//以上为读写优化
signed main()
{
	freopen("1.txt","r",stdin);
	n=read();
	for(register int i=1;i<=n;i++) p[i].h=read(),p[i].i=i;//输入
	sort(p+1,p+1+n,cmp);//排序
	for(register int i=1;i<=n;i++) p[i].l=i-1,p[i].r=i+1,pos[p[i].i]=i;//链表日常
	p[1].l=0;p[n].r=0;//初始化
	for(register int i=1;i<=n;i++)//这里是求ga和gb
	{
		j=pos[i];l=p[j].l;r=p[j].r;
		if(zuo()) ga[i]=nearly(p[l].l,r),gb[i]=p[l].i;
		else ga[i]=nearly(l,p[r].r),gb[i]=p[r].i;
		if(l) p[l].r=r;
		if(r) p[r].l=l;//记得删除
	}
	for(register int i=1;i<=n;i++)//初始化第一天
	{
		if(ga[i]) f[0][i][0]=ga[i],da[0][i][0]=dist(i,ga[i]);
		if(gb[i]) f[0][i][1]=gb[i],db[0][i][1]=dist(i,gb[i]);
		da[0][i][1]=db[0][i][0]=0;
	}
	for(register int i=1;i<=20;i++)//以下为动态转移
	 for(register int j=1;j<=n;j++)
	  for(register int k=0;k<2;k++)
	{
		if(i==1) o=k^1;else o=k;
		if(f[i-1][j][k]) f[i][j][k]=f[i-1][f[i-1][j][k]][o];
		if(f[i][j][k])
		da[i][j][k]=da[i-1][j][k]+da[i-1][f[i-1][j][k]][o],
		db[i][j][k]=db[i-1][j][k]+db[i-1][f[i-1][j][k]][o];
	}
	x0=read();m=read();
	for(register int i=1;i<=n;i++)
	{ 
		solve(x0,i);
		if(lb&&1.0*la/lb<minn)
		{
			minn=1.0*la/lb;
			ans=i;
		}
	}
	write(ans);putchar(10);
	while(m--)
	{
		j=read();x=read();
		solve(x,j);
		write(la);putchar(32);write(lb);putchar(10);
	}
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/88901165