【双向链表】【倍增】开车旅行

Luogu P1081

题目:

A,B轮流走(A先)
A走前方次近的点,B走前方最近的点
距离为: ∣ a i − a j ∣ |a_i-a_j| aiaj
1.从哪个点出发,总距离<x,A与B的行走距离比值最小
2.从s出发,总距离<x,A,B分别的行走距离

题解:

暴力一般直接找每个起点对应A,B会到达的点

预处理:排序后,链表,找其前面后面两个范围内的点。(本题重点)
可以发现不论前面怎么走只要以此为起点每次的终点都是不会改变的,于是我们可以用倍增简化每次单跳的操作
倍增不仅要维护跳的点,还要维护A,B分别走了多少


恶心调了几天,终于调出来了。。。
对拍,调错,改动。。。


写的可能很丑
b z [ i ] [ j ] 表 示 以 i 为 起 点 跳 2 j 次 所 到 达 的 点 bz[i][j]表示以i为起点跳2^j次所到达的点 bz[i][j]i2j
g [ i ] [ j ] [ s ] [ t ] 表 示 以 i 为 起 点 s 先 跳 , 跳 2 j 次 中 t 走 的 距 离 g[i][j][s][t]表示以i为起点s先跳,跳2^j次中t走的距离 g[i][j][s][t]is2jt
似乎很绕,但总算绕出来了

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,INF=1e18;
int n;
int a[N];
int bz[N][20][2],g[N][20][2][2];
struct qu{int id,num;bool operator<(const qu x)const{return num<x.num;}}b[N];
int we[N];
int lst[N],nex[N],t[N];
void putin(int x,int c){lst[x]=x-1;nex[x-1]=x;t[x]=c;}
void del(int x){lst[nex[x]]=lst[x];nex[lst[x]]=nex[x];nex[x]=0;}
void init()
{
	for(int i=1;i<=n;i++)
	b[i].id=i,b[i].num=a[i];
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++)
	we[b[i].id]=i,putin(i,b[i].id);
	int fw[5];
	for(int i=1;i<n;i++)
	{
		int k=we[i];
		fw[1]=lst[k];fw[2]=lst[lst[k]];fw[3]=nex[k];fw[4]=nex[nex[k]];
		g[i][0][1][1]=g[i][0][0][0]=INF;
		for(int j=1;j<=4;j++)
		{
			if(!fw[j])continue;
			int now=t[fw[j]];
			int dis=abs(a[i]-a[now]);
			if(g[i][0][0][0]>dis||(g[i][0][0][0]==dis&&a[bz[i][0][0]]>a[now]))
			{
				bz[i][0][1]=bz[i][0][0];
				g[i][0][1][1]=g[i][0][0][0];
				bz[i][0][0]=now;
				g[i][0][0][0]=dis;
			}
			else if(g[i][0][1][1]>dis||(g[i][0][1][1]==dis&&a[bz[i][0][1]]>a[now]))
			{
				bz[i][0][1]=now;
				g[i][0][1][1]=dis;
			}
		}
		del(k);
	}
}
void get_bz()
{
	bz[n-1][0][1]=0;//可能是写法问题,不写这句就会出错
		for(int i=1;i<=n;i++)
		{
			int k0=bz[i][0][0],k1=bz[i][0][1];
			bz[i][1][0]=bz[k0][0][1];
				g[i][1][0][0]=g[i][0][0][0]+g[k0][0][1][0];
				g[i][1][0][1]=g[i][0][0][1]+g[k0][0][1][1];
			bz[i][1][1]=bz[k1][0][0];
				g[i][1][1][0]=g[i][0][1][0]+g[k1][0][0][0];
				g[i][1][1][1]=g[i][0][1][1]+g[k1][0][0][1];
		}//A跳完2^0次B跳
	for(int j=2;j<=18;j++)
	for(int i=1;i<=n;i++)
	{
		int k0=bz[i][j-1][0],k1=bz[i][j-1][1];
		bz[i][j][0]=bz[k0][j-1][0];
			g[i][j][0][0]=g[i][j-1][0][0]+g[k0][j-1][0][0];
			g[i][j][0][1]=g[i][j-1][0][1]+g[k0][j-1][0][1];
		bz[i][j][1]=bz[k1][j-1][1];
			g[i][j][1][0]=g[i][j-1][1][0]+g[k1][j-1][1][0];
			g[i][j][1][1]=g[i][j-1][1][1]+g[k1][j-1][1][1];
	}//A跳完2^i(i>=1)次还是A跳
}
int dis[2];
void f(int x,int up)
{
	int tim=1;dis[0]=0;dis[1]=0;
	for(int i=18;i>=0;i--)
	{
		//0:B 1:A
		if(!bz[x][i][tim])continue;
		int tmp=g[x][i][tim][0]+g[x][i][tim][1];
		if(tmp+dis[0]+dis[1]<=up)
		{
			dis[0]+=g[x][i][tim][0];
			dis[1]+=g[x][i][tim][1];
			x=bz[x][i][tim];
		}
	}
	return ;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	init();
	get_bz();
	int x,s;
	scanf("%lld",&x);
	double cas=1e9;int bj=1;
	for(int i=1;i<=n;i++)
	{
		f(i,x);
		//0:B 1:A
		double tmp;
		if(!dis[0]&&!dis[1])continue;
		if(!dis[0])tmp=1e9;
		else tmp=dis[1]*1.0/dis[0];
		if(tmp<cas)
		{
			cas=tmp;
			bj=i;
		}
	}printf("%lld\n",bj);
	int q;
	scanf("%lld",&q);
	for(int k=1;k<=q;k++)
	{
		scanf("%lld%lld",&s,&x);
		f(s,x);
		printf("%lld %lld\n",dis[1],dis[0]);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值