NOIP2012 开车旅行 (倍增)

2 篇文章 0 订阅
1 篇文章 0 订阅

题意:一行N个城市,有各自不同的海拔,定义两个城市之间的距离为海拔之差的绝对值,小a和小b轮流开车,开车方向从左往右,小a总是开到第二近的城市,小b开到最近的城市(如有两个城市和当前城市海拔之差相等,海拔低的更近)。当其中任一人无法按照自己的方案前进或前进后总路程超过一个上限,旅行结束。一,给定一个路程上限x0,求从哪个城市出发A和B路程比值最小,这里规定任意数比零等于无穷大,比值相等输出海拔最高的。二,给出M组询问,每组给出出发城市s和路程上限x,求A和B的路程。N<=100000,M<=10000.

最直观的思路应该是先N^2预处理在每个城市,a和b的目标是哪里,然后再O(NM)地回答询问。看一下数据范围,70分N<1000, 这个暴力的得分是相当可观的。在考场上最好不管三七二十一先写出来,因为很明显这道题并不是针对某一个成型的算法出的,正解肯定是在这个暴力基础上优化。

先观察一下这个暴力的复杂度O(N^2+MN),如果要过这道题,显然两项都要优化。对于第一项,优化比较好办,倒序扫描,将每个城市压入平衡树,对于每个海拔求一下前驱后继就行了,降为nlogn(在下无能,实在想不出类似于排序扫描之类的更优的方法)。

然后第二个环节,不难发现在每个城市,每个人的目标都各自是固定的,实现时就可以将两个人一起开一轮绑定在一起,这样一来每个城市的后两个,三个等等的目标城市都是固定的,这提示我们可以处理每个城市后面相距多个轮回的城市以加速算法。对于这种问题,比较经典的想法就是倍增(logn)或者分块(sqrt(n))。

个人感觉分块是更好写的,假设N=100000,sqrt(N)约等于350,我们就处理出每个点之后第350个城市是哪个,然后就是经典的两行代码:while(x>350)x-=350,city=next350[city],a+=..., b+=...; while(x>0)x--,city=next1[city],x+=..., b+=...;(当然实际操作中还要处理是a开车还是b开车,是否城市走完了路程限制还没消耗完这些细节)两个循环次数都在350以内,足以过最大数据了。当然,这个350只是个例子,实际操作中取根号n即可(就取350当然也不会超时。。)。

当然,分块的目的只是为了多得点分甚至水过这道题,最正统的当然还是倍增了,处理每个城市第2^k个目标点,循环的时候,指数从大到小枚举即可保证二进制位能够凑成原来的整数。但是如果实现得不是很好(向我这种),倍增完了往往到不了目标点,还需要特判并且手动往后走一下。

这题虽然理论上路程之和完全会超int,然而实践int可以过,不需要开long long,CCF真是良心数据。但是考场上一定要开long long,不能被NOIP的数据惯坏了(永远不会忘记CQOI2015送分裸题爆零的经历)。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
#define dis(i,j) (h[i]>h[j] ? h[i]-h[j] : h[j]-h[i])
const int MAXN = 100010;
int N, M, LG;
int h[MAXN];
int nextb[MAXN], nexta[MAXN];
#define _next(t) nextb[nexta[t]]
int ans[MAXN][2];

struct City {
	int h, id;
	City(){}
	City(int x,int y){h=x; id=y;}
	bool operator < (const City&t1) const {
		return h < t1.h;
	}
};

set<City> S;
set<City>::iterator it1, it2, bst;
void FindNear()//用set很容易RE,一定要多对拍一下
{
	nextb[N-1] = N;
	S.insert(City(h[N], N));
	S.insert(City(h[N-1], N-1));
	for (int i = N-2; i>0; --i)
	{
		City tmp = City(h[i], i), tmp2;
		it1 = S.lower_bound(tmp);
		if (it1 == S.end()) {
			--it1; nextb[i] = it1->id;
			--it1; nexta[i] = it1->id;
			S.insert(tmp);
			continue;
		}
		if (it1 != S.begin()) {
			it2 = it1; --it1;
			if (it2->h - h[i] < h[i] - it1->h) nextb[i] = it2->id, bst = it2;
			else nextb[i] = it1->id, bst = it1;
		}
		else {
			nextb[i] = it1->id;
			++it1; nexta[i] = it1->id;
			S.insert(tmp); continue;
		}
		tmp2 = *bst;
		S.erase(bst);
		it1 = S.lower_bound(tmp);
		if (it1 == S.end()) { --it1; nexta[i] = it1->id; }
		else {
			if (it1 != S.begin()) {
				it2 = it1; --it1;
				if (it2->h - h[i] < h[i] - it1->h) nexta[i] = it2->id;
				else nexta[i] = it1->id;
			}
			else nexta[i] = it1->id;
		}
		S.insert(tmp2);
		S.insert(tmp);
	}
}

int adis[MAXN][20], bdis[MAXN][20];
int dest[MAXN][20];
void Prepare()
{
	int i, j;
	for (LG=1; 2*(1<<LG)<=N; ++LG) ;
	for (i = 1; i<=N; ++i) {
		adis[i][0] = nexta[i] ? dis(i, nexta[i]) : 0;
		bdis[i][0] = _next(i) ? dis(nexta[i], _next(i)) : 0;
		dest[i][0] = _next(i);
	}
	for (i = 1; i<=LG; ++i)
		for (j = 1; j<=N; ++j) {
			dest[j][i] = dest[ dest[j][i-1] ][i-1];
			adis[j][i] = adis[j][i-1] + adis[ dest[j][i-1] ][i-1];
			bdis[j][i] = bdis[j][i-1] + bdis[ dest[j][i-1] ][i-1];
		}
}

void drive(int s, int len, int& xa, int& xb)
{
	xa = xb = 0;
	int cur = s;
	for (int i = LG; i>=0; --i)
		if (dest[cur][i] && adis[cur][i]+bdis[cur][i]<=len) {
			xa += adis[cur][i];
			xb += bdis[cur][i];
			len -= adis[cur][i] + bdis[cur][i];
			cur = dest[cur][i];
		}
	while (1) {
		if (dis(nexta[cur], cur)>len || !nexta[cur]) break;
		xa += dis(nexta[cur], cur);
		len -= dis(nexta[cur], cur);
		cur = nexta[cur];
		if (dis(nextb[cur], cur)>len || !nextb[cur]) break;
		xb += dis(nextb[cur], cur);
		len -= dis(nextb[cur], cur);
		cur = nextb[cur];
	}
}

int X0;
const double eps = 1e-6, DINF = 1e12;//不卡精度,double可水过
int task1()
{
	double best = DINF, cur;
	int res=0, i, xa, xb;
	for (i = 1; i<=N; ++i) {
		drive(i, X0, xa, xb);
		if (xb == 0 && best>=DINF-eps) {
			if (h[i] > h[res]) res = i;
			continue;
		}
		cur = (double)xa / (double)xb;
		if (cur < best-eps) best=cur, res=i;
		else if (cur>=best-eps && cur<=best+eps && h[i]>h[res])
			best = cur, res = i;
	}
	return res;
}

int main()
{
	int i, s, len, xa, xb;
	scanf("%d", &N);
	for (i = 1; i<=N; ++i)
		scanf("%d", &h[i]);
	FindNear();
	Prepare();
	scanf("%d%d", &X0, &M);
	for (i = 1; i<=M; ++i)
	{
		scanf("%d%d", &s, &len);
		drive(s, len, xa, xb);
		ans[i][0] = xa;
		ans[i][1] = xb;
	}
	printf("%d\n", task1());
	for (i = 1; i<=M; ++i)
		printf("%d %d\n", ans[i][0], ans[i][1]);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值