7485-旅行问题


本文总阅读量

题目传送
环形问题,先破环成链,即用2倍长度的线性序列代替环形。
例如,假设n=5,环形序列为1,2,3,4,52倍长度序列为1,2,3,4,5,1,2,3,4,5
序列编号为110,则求从编号1的点顺时针出发能否回到编号1的点,就是求从编号1的点出发能否到达编号6的点,求从编号5的点逆时针出发能否回到编号5的点,就是求从编号10的点出发能否到达编号5的点。
能否到达取决于油量是否够,所以途中的任意一个加油站,油量必须大于等于路程,才能到达下一个加油站。
p[i]表示第i个加油站可以加的油量,d[i]表示从第i个点走到第i+1个点的路程,则p[i]d[i]就表示从第i个点到第i+1个点油量的增减情况。
先考虑顺时针情况,s[i]=s[i1]+p[i]d[i]表示剩余油量的前缀和,也就是说s[i]表示刚走到第i+1个点还未加油时汽车的剩余油量,如果油是负数,则说明无法到达。
那么在区间[i,n+i]n个加油站,每个站点的剩余油量都不能为负数,否则无法到达。所以会想到枚举这些区间和,如果油负数,则无法到达,但是这样时间复杂度很高。其实我们只要找最小的前缀和,找到最小的前缀和不就是最小的区间和吗?那么在一个区间中寻找最小值,就可以使用单调队列优化
破环成链和前缀和

	for(int i = 1; i <= n; i++){
		cin>>p[i]>>d[i];
		p[n + i] = p[i];
		d[n + i] = d[i];
	}
	for(int i = 1; i <= 2 * n; i++) sum[i] = sum[i-1] + p[i] - d[i];

顺时针

顺时针,单调队列优化,滑动窗口做法。

	int head = 0, tail = 0;
	for(int i = 1; i < 2 * n; i++){
		while(head <= tail && i - q[head] >= n) head++;
		while(head <= tail && sum[q[tail]] >= sum[i]) tail--;
		q[++tail] = i;
		if(i >= n && sum[q[head]]-sum[i-n] >= 0) ans[i - n + 1] = 1;  //i为1的时候表示走向2,所以i为n的时候表示走向1
	}

因为是顺时针,所以当i=n的时候,表示回到原点。需要注意的是,第i=n的时候,表示走回加油站1
sum[q[head]]sum[in]计算的是区间和,如果最小的区间和大于等于0,那么其他区间和大于0,就符合到任意加油站,剩余油量都不为0,也就能返回起点。

逆时针

逆时针,可以先计算出后缀和,然后从后往前扫描,同时进行单调队列优化,大致差不多,细节需注意,毕竟方向反了,代码不可能一样。
后缀和计算方式,第i个点逆时针行驶,油量为p[i],但距离不是d[i],因为d[i]表示ii+1,现在是ii1,所以距离在d[i1]
所以后缀和计算如下:

for(int i = 2 * n; i > 0; i--) sum[i] = sum[i + 1] + p[i] - d[i - 1];

反向扫描,单调队列优化,注意方向。

	head = 0, tail = 0;q[head] = 2 * n + 1; //初始化
	for(int i = 2 * n; i > 1; i--){
		while(head <= tail && q[head] - i >= n) head++; //如果不初始化,q[head]-i 就不能正确计算区间长度
		while(head <= tail && sum[q[tail]] >= sum[i]) tail--;
		q[++tail] = i;
		if(i <= n + 1 && sum[q[head]] >= sum[i + n]) ans[i - 1] = 1;
	}

i=n+1的时候,就是要走向n,因为是逆时针,所以条件为in+1 ,对应返回的位置i1
还要注意的是head要初始化为2n+1,否则q[head]in计算要出问题,即不初始化为2n1,会算出负数,显然是错误的。


本站总访问量