POI2005·洛谷·A Journey To Mars

初见安~这里是传送门:洛谷P3422

Description

Byteazar 决定去火星参加一个空间站旅行. 火星的所有空间站都位于一个圆上. Byteazar 在其中一个登陆然后变
开始饶圈旅行. 旅行需要耗费油料,一升油料只能跑1米,每个空间站可以补给的油料都有所不同. Byteazar 每到一
个空间站便可以把该空间站的油料全部拿走.(他的油箱是没有容量限制的) 但是如果走到某个时候突然没油了那么
旅行便失败了. Byteazar 需要决定要在哪个地方登陆使得他能顺利访问完所有的空间站后回到他当初登陆的地方.
一个细节是他登陆后可以选择两个方向中的任意一个进行旅行.

Input

第一行是一个数N (3 <= N <= 1 000 000). 表示空间站的总数.
空间站从1 到 N标号.
接下来N 行每行两个数pi 和 di (pi<= 0, di > 0).
表示第i个空间站所储存的油料以及第i个到第i + 1个空间站之间的距离( dN 表示第N个空间站到第1个空间站的距离).
所有的油料以及所有的距离的和保证不超过2 000 000 000.

Output

输出n行,如果从第i个空间站登陆可以走遍所有的空间站那么打印TAK (YES is Polish), 否则打印NIE (NO in Polish).

Sample Input

5
3 1
1 2
5 2
0 1
5 4

Sample Output

TAK
NIE
TAK
NIE
TAK

//代码有参考题解

Sol

听说是用单调队列。【大雾】完全找不到关系QwQ所以很委屈【啪】

因为从空间站i出发,可左可右,所以我们不管用什么操作都一定是要做两次的。【基本框架】然后——从i出发,到达一个站,我们只需要看在到达之前——也就是获得该站的补给之前,你的油的值不会小于0。看到N这么大估计也只有用O(N)的算法了,所以想到递推——写出来就和DP差不多了。

我们设一个数组f[ ] [ 2 ],f[ i ] [ 0 ]表示从空间站 i 出发最远可以到哪里,f[ i ][ 1 ]表示到那里时剩多少油。我们在递推(或者说DP)的时候可以顺便保证油一定大于0,能让你到达f存的最远的空间站,所以最后我们只要判定到达可及的最远的空间站时有没有环绕过所有的站一圈即可

思路如上,很简单【没懂的话熟读定义后看代码也是可以懂的】。但是信息处理是有坑的——注意题目给出的距离d的定义——第i个到第i + 1个空间站之间的距离。也就是说我们在处理反向的时候——也就是把存的内容reverse(翻转)过来的时候要注意下标也要集体左移一个单位。(手动画一下就可以明白了)当然直接在运算的时候处理也可以,但是这样的话两次操作就几乎没有重叠部分,会使得代码量较大。所以这里还是特殊处理同化一下了。

当然,处理环形区间,是要复制一遍接在后面操作的,也就是说全程的maxn都应该用n * 2。

下面是代码及详解~

#include<bits/stdc++.h>
#define maxn 2000010//n取两倍
#define ll long long
using namespace std;
int n;
ll d[maxn], p[maxn], f[maxn][2], ans[maxn];
//0 oil 1 how far
void solve(bool flag)
{
	memset(f, 0, sizeof f);
	f[n << 1][0] = p[n << 1];//一个简单的初始化
	f[n << 1][1] = (n << 1);
	for(int i = 2 * n - 1; i >= 1; i--)
	{
		f[i][0] = p[i], f[i][1] = i;//初始化
		while(f[i][0] >= d[f[i][1]] && f[i][1] + 1 <= (n << 1))//可以到达 并且 没有越界
		{
			f[i][0] = f[f[i][1] + 1][0] + f[i][0] - d[f[i][1]];//远一站,补给,耗油。
			f[i][1] = f[f[i][1] + 1][1];//远一站
		}
	}
	
	if(flag)//分正反向
		for(int i = 1; i <= n; i++)
			ans[i] |= (f[i][1] - i >= n);
	else
		for(int i = 1; i <= n; i++)
			ans[n - i + 1] |= (f[i][1] - i >= n);//从i出发,所以所及之站要减去初始。
}

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
	{
		scanf("%lld%lld", &p[i], &d[i]);
		p[i + n] = p[i], d[i + n] = d[i];//环形
	}
	
	solve(1);//正向直接处理
	
	reverse(p + 1, p + 1 + (n << 1));
	reverse(d + 1, d + 1 + (n << 1));//翻转
	
	d[n << 1 | 1] = d[1];
	for(int i = 1; i <= (n << 1); i++)//特殊处理
		d[i] = d[i + 1];
		
	solve(0);//反向

	for(int i = 1; i <= n; i++)
	{
		if(ans[i]) puts("TAK");//ans可以设置成bool
		else puts("NIE");
	}
	return 0;
}

本来想用线段树的,但是好像条件不好操作,所以就没有用……

迎评:)
——End——

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值