UPC-穿越

山再高,往上爬,总能登顶;
路再长,走下去,定能到达。

穿越-时间轴上的bfs

题目描述

亚马逊雨林实在是太大了,小X和他的小弟们进去一会儿就迷路了,然而大雨已经来临,冲刷了一些道路,小X凭借他最后的5%的电量给你发来一条求助信息,希望你帮助他们逃出困境……
小X给你发来一张n×m的地图,每一个点有4种情况。
“0”:此地方可以走。
“1”:此地方不可以走。
“2”:此地方有一种凶恶的野兽。
“3”:此地方为传送地域。
野兽会不定时地苏醒过来,此阶段该处就不能走。
暴雨会不定时地冲刷一些地区,这些地区从今往后不可以行走,也不可以传送到。
传送地域之间可以互相传送,即可以从一个传送门传送到任何一个其他的传送门。
小X和他的小弟们现在位于(1,1),他们希望在(n,m)点逃出雨林。
他们只能前后左右移动,每移动一次需要花费1个单位的时间,传送一次需要花费2个单位的时间,也可以选择不传送。
注意:若下一秒某地区暴雨即将冲刷/野兽即将醒来,则不可以通行,也不可停留在这个这个地区。
在任意时刻,他们可以选择不进行任何操作。

输入

第一行两个整数n,m,表示一张n×m的地图。
接下来n行,每行m个整数,为0-3之间的一个数字。
接下来一行一个整数a,表示接下来a行描述暴雨情况。
接下来a行,每行第一个整数为t,表示此次暴雨在t时刻来临;第二个整数为p,表示此次暴雨冲刷了p个地区;接下来p组整数,每组有(x,y)两个整数,表示(x,y)这个地点被冲刷。(假设暴雨冲刷不需要时间,同一个地点可能被暴雨多次冲刷)
接下来b行,每行前两个整数为t1,t2,表示这个野兽在t1-t2时刻是苏醒的。接下来两个整数x,y,表示野兽位于x,y位置。(保证(x,y)=2)
不保证所有的野兽均会有苏醒的时刻
保证:(1,1)=(n,m)=0且永远不会被暴雨冲刷。

输出

一行一个整数,即最短逃脱时间。
(保证小X和他的小弟们可以逃脱亚马逊雨林)

Sample Input

3 3
0 1 0
2 0 1
3 2 0
2
2 1 3 1
1 1 1 3
1
2 4 2 1

Sample Output

4

Hint

时刻1,暴雨冲刷了1,3这个位置。
时刻2,暴雨冲刷了3,1这个位置。
时刻2-4,位于(2,1)的野兽苏醒了。
小X与他的小弟们的逃脱路线:
(1,1)->(2,1)->(2,2)->(3,2)->(3,3)
【数据范围】
对于100%的数据,n≤300,m≤300,t≤10000。

首先感谢一波CoolGuang学长的耐心教学。
我不生产知识我只是知识的搬运工。。。
思路分析
这是一个时间轴上的bfs,那么时间尤为重要。而普通bfs中第一个到达就最优的做法就显得有些不妥。用更新最优时间的方法来解决,可以很好的解决多个传送门何时到的问题,以及可以什么时候到的问题。直接第一个到达为最优解的话很难控制时间。
我门可以吧传送门专门存起来,因为传送门都可以互相传送,如果可以传送的话,这样的话我们需要遍历所有传送门更新最优解。
洪水假设我们终将都会到来,只是有的到来的时间无穷大,有的很接近现在而已
然后我们也就是分类讨论
1.空地
2.有野兽
3.传送门
传送门已经说过了,前提是你必须得在可以传的条件下传
而且你得分成我走不走传送门
空地直接更新最优解就
而野兽的情况就有点意思
首先呢
如果你不在苏醒的时间内
那么直接最优解更新
如果在的话,那么你可以在原来的位置等到睡着再过去,但你不能等到洪水来了
否则直接更新最优解
思路就是如此,代码有注释解析
代码如下
AC时间到

#include
#include
#include
#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma warning(disable:4244)
#define PI 3.1415926536
#pragma GCC optimize(2)
#define accelerate cin.tie(NULL);cout.tie(NULL);ios::sync_with_stdio(false);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll ll_inf = 9223372036854775807;
const int int_inf = 2147483647;
const short short_inf = 32767;
const char char_inf = 127;
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
inline ll read() {
	ll c = getchar(), Nig = 1, x = 0;
	while (!isdigit(c) && c != '-')c = getchar();
	if (c == '-')Nig = -1, c = getchar();
	while (isdigit(c))x = ((x << 1) + (x << 3)) + (c ^ '0'), c = getchar();
	return Nig * x;
}
inline void out(ll a) {
	if (a < 0)putchar('-'), a = -a;
	if (a >= 10)out(a / 10);
	putchar(a % 10 + '0');
}
ll qpow(ll x, ll n, ll mod) {
	ll res = 1;
	while (n > 0) {
		if (n & 1)res = (res * x) % mod;
		x = (x * x) % mod;
		n >>= 1;
	}
	return res;
}
#define Floyd for(int k = 1; k <= n; k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
#define read read()
struct Point {
	int x, y;
};
short sta[350][350];//每一点的状态
int Time[350][350], st[350][350], ed[350][350], dis[350][350];// Time为洪水到来的时间,st是野兽苏醒的时间,ed是野兽睡下的时间,dis是当前最优时间
queue<Point>q;//bfs中的队列
vector<Point>por;//传送门portal
Point temp;//读取当前的点
int n, m;
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };
bool check(int x, int y)//检测是否边界内
{
	if (x <= n && x >= 1 && y <= m && y >= 1)return true;
	return false;
}
void bfs()
{
	//因为要走时间线,那么最重要的就是更新时间点。通常直接第一个到的就不好用了
	//初始化所有的时间,目前都是∞
	memset(dis, 127, sizeof(dis));//这就是最大值
	dis[1][1] = 0;//当前位置为初始点,dis=0;
	q.push(Point{ 1,1 });
	while (q.size())
	{
		temp = q.front();
		q.pop();
		for (int i = 0; i < 4; i++)
		{
			int x = temp.x, y = temp.y;
			int nx = temp.x + dx[i];
			int ny = temp.y + dy[i];//走的方向的坐标已确认
			if (check(nx, ny) && sta[nx][ny] != 1)//完全可行无路障
			{
				if (sta[nx][ny] == 0) {//空地
					if (dis[x][y] + 1 < Time[nx][ny] && dis[nx][ny] > dis[x][y] + 1)//暴雨之前,而且更优
					{
						dis[nx][ny] = dis[x][y] + 1;
						q.push(Point{ nx, ny });
					}
				}
				else if (sta[nx][ny] == 2)//野兽
				{
					if (dis[x][y] + 1 < Time[nx][ny])//暴雨来临之前
					{
						if (dis[x][y] + 1 >= st[nx][ny] && dis[x][y] + 1 <= ed[nx][ny])//如果卡时醒着时间内
						{
							//睡着是ed[nx][ny]那么睡着后就是ed[nx][ny]+1
							if (dis[nx][ny] > ed[nx][ny] + 1 && ed[nx][ny] + 1 < Time[nx][ny]) {//等到睡着之后再走
								dis[nx][ny] = ed[nx][ny] + 1;//入队列 
								q.push({ nx,ny });
							}
						}
						else if (dis[nx][ny] > dis[x][y] + 1)//没卡时间内
						{
							dis[nx][ny] = dis[x][y] + 1;//更新状态
							q.push(Point{ nx,ny });
						}
					}
				}
				else if (sta[nx][ny] == 3)
				{
					//不走传送门的话
					if (dis[x][y] + 1 < Time[nx][ny] && dis[nx][ny] > dis[x][y] + 1) {///当前不会被冲刷
						dis[nx][ny] = dis[x][y] + 1;
						q.push({ nx,ny });
					}
					//走传送门
					if (dis[x][y] + 1 < Time[nx][ny])
					{
						for (auto id : por)
						{
							int Tx = id.x, Ty = id.y;
							if (dis[x][y] + 3 < Time[Tx][Ty] && dis[x][y] + 3 < dis[Tx][Ty])//用传送门的话走到传送门使用传送门三个时间点
							{
								dis[Tx][Ty] = dis[x][y] + 3;
								q.push(Point{ Tx, Ty });
							}
						}
					}
				}
			}
		}

	}
}
int main()
{
	n = read, m = read;
	memset(Time, 127, sizeof(Time));
	for (int i = 1; i <= n; i++)//记录传送门,更新路障,初始化暴雨时长
		for (int j = 1; j <= m; j++)
		{
			scanf("%d", &sta[i][j]);
			if (sta[i][j] == 3)por.push_back(Point{ i,j });
		}
	int N = read;
	for (int i = 1; i <= N; i++)//更新暴雨时间
	{
		int t;
		int num;
		scanf("%d%d", &t, &num);
		int x, y;
		for (int j = 1; j <= num; j++)
		{
			scanf("%d%d", &x, &y);
			Time[x][y] = min(Time[x][y], t);
		}
	}
	N = read;
	for (int i = 1; i <= N; i++)
	{
		int stime, etime, x, y;//野兽苏醒时间
		scanf("%d%d%d%d", &stime, &etime, &x, &y);
		st[x][y] = stime;
		ed[x][y] = etime;
	}
	bfs();
	cout << dis[n][m] << endl;
}

在此鸣谢CoolGuang!

By-轮月
END

你可能感兴趣的:(算法,中国石油大学OJ,ACM)