题解:P14065 [PO Final 2022] 对弈 / Laserschack

题目传送门

先警示后人:

我在看题目的时候看成了 \(1 \le r,c \le 4000\)

然后被迫想了一个 \(\operatorname{O}(\operatorname{RC} \ \operatorname{log} \ \operatorname{RC})\) 发现好像有点玄

最后乱加一堆优化跑爆了只能重写


正解:

  • 对于这个数组考虑 二维 转 一维 ,静态数组存不下
    如果喜欢用动态二维数组自然是很好的()
  • 算法上考虑两遍 BFS 直接秒了,下面开始介绍思路

第一遍 BFS:

我们多源 BFS 对所有 R 进行扩展

更新图上每个点最早被烟雾覆盖的时间 \(\operatorname{time[\ ]}\)

BFS 的特性可以保证这样遍历到的每个点确实是正确的
\(\operatorname{O(RC)}\)

第二遍 BFS:

从 A 开始或从 K 开始都无所谓,我喜欢反图就从 K 开始了

  • 我们的目标:

    找到所有合法路径(即能从 A 到达 K 的路径)最晚被烟雾碰到的那条路,并且求出这条路上最小的 \(\operatorname{time[\ ]}\)

  • 实现方式:

    从 K 开始遍历四个方向

    如果找到了边界,就 break

    如果找到了 o ,说明找到一条子路了,就判断当前子路存的 path_time[ ] 是否小于当前手里搜出来的最小断点,如果是,就拿手里这个更新这个子路的 path_time[ ]

    如果找到了 A ,说明走通了一条合法路径,我们拿手里的最小断点直接去 \(\max(\operatorname{ans,current})\)

    而这个 BFS 的队列的存储要使用大根堆的优先队列来尽可能的贪心,就像 dijkstra 选取最小边来扩展

    我们从队列里面拿出任意一个点的时候,将他的最早断点与此时的 ans 对比,如果这个最早断点早于 ans ,那么直接可以 continue

    这时 \(\operatorname{O(RC \ log \ RC)}\)

  • 正确性简证:

    激光不经过任何烟雾,从而攻击到国王。

    也就是所有 A 到 K 的合法路径上都存在烟雾,求这个最早的时间

    转换成不以时间为变量,求所有路径上最早断点最晚的路径,当这个路径被遮住是,其他所有路径也早就被遮住

细节都在代码里了

注释清晰,码风优良


\(\mathbb {\LARGE C \large _O \ \Large ^D \normalsize _E}\)

cpp 复制代码
#include<bits/stdc++.h>

using namespace std;

const int N = 4002;
const int dx[5] = {0, 1, -1, 0, 0};
const int dy[5] = {0, 0, 0, 1, -1};

struct SMOKE {
	int x, y;
};

queue< SMOKE >R;//存储R的坐标,用于多源BFS预处理图 

int r, c, in_x, in_y, pth_min;//in_x,in_y存国王的坐标 
int a[N << 4], SMoke_TIme[N << 4], vis[N << 4]; //a存图; SMoke_TIme存烟雾的时间层; vis存当前点(只有镜子才会有)的最早断点 
int ans = 0;
int x , y, xx, yy, v;

void input(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	cin >> r>> c;
	for(int i = 1; i <= r; i++){
		for(int j = 1; j <= c; j++){
			SMoke_TIme[c * (i - 1) + j] = -1;
			char ch;
			cin >> ch;
			if(ch == 'o') a[c * (i - 1) + j] = 4;
			if(ch == '.') a[c * (i - 1) + j] = 0;
			if(ch == 'A') a[c * (i - 1) + j] = 1;
			if(ch == 'K') {
					      a[c * (i - 1) + j] = 2;
				in_x = i;
				in_y = j;//存储国王位置 
			}
			if(ch == 'R') {
					      a[c * (i - 1) + j] = 3;
				R.push(SMOKE{i, j});//烟雾初始位置入队保存 
				SMoke_TIme[c * (i - 1) + j] = 0;
			}
		}
	}
}

void output(){
	cout << ans << endl;
}

void BFS(){//多源BFS预处理图的时间层 
	
	while( !R.empty()){
		x = R.front().x;
		y = R.front().y;
		R.pop();
		for(int i = 1; i <= 4; i++){//四个方向扩散 
			xx = x + dx[i];
			yy = y + dy[i];
			if(xx < 1 or yy < 1 or xx > r or yy > c or SMoke_TIme[c * (xx - 1) + yy] != -1)continue;//边界条件
			//BFS能保证每个点搜到的时候都是距离他最近的烟雾为祖先
			//也就是能保证SMoke_TIme最小 
			SMoke_TIme[c * (xx - 1) + yy] = SMoke_TIme[c * (x - 1) + y] + 1;//是自己祖先的下一秒 
			R.push(SMOKE{xx, yy});
		}
	}
}

struct path{
	int x, y;
	int v;//到这个镜子的这条路径上的最早阻断点,也就是这条路最早失效的时间 
	bool operator <(const path &other)const {
		return v >= other.v;//优先队列取出当前最优解(就是最晚被干掉的合法路径) 
	}
};

priority_queue< path >q;

void solve_BFS(){//从国王位置倒着搜所有合法路径 
	
	for(int i = 1; i <= 4; i++){
		xx = in_x;
		yy = in_y;
		pth_min = SMoke_TIme[c * (xx - 1) + yy];
		while(true){
			xx += dx[i];
			yy += dy[i];
			if(xx < 1 or yy < 1 or xx > r or yy > c)break;//边界条件判断 
			
			if(a[c * (xx - 1) + yy] == 3) break;//剪枝 
			
			pth_min = min(pth_min, SMoke_TIme[c * (xx - 1) + yy]);
			
			if(a[c * (xx - 1) + yy] == 4){//遇到镜子就停止这个方向的初始化
				vis[c * (xx - 1) + yy] = max(vis[c * (xx - 1) + yy], pth_min);
				q.push(path{xx, yy, pth_min}); 
				break;
			}
			if(a[c * (xx - 1) + yy] == 1){//遇到A就说明可以直接返回这条路线了 
				ans = max(ans, pth_min);
				break;
			}
		}
	}
	
	while(!q.empty()){
		path t = q.top();
		q.pop();
		if(t.v <= ans)continue;//剪枝优化:当前这条路径已经废了,被阻挡的时间比我们已经求出来的还早 
		for(int i = 1; i <= 4; i++){
			xx = t.x;
			yy = t.y;
			pth_min = t.v;
			while(true){
				xx += dx[i];
				yy += dy[i];
				if(xx < 1 or yy < 1 or xx > r or yy > c) break;//边界条件判断
				
				if(a[c * (xx - 1) + yy] == 3)break;//剪枝 
				
				pth_min = min(pth_min, SMoke_TIme[c * (xx - 1) + yy]);//更新路径 
				
				if(a[c * (xx - 1) + yy] == 4){
					if(pth_min > vis[c * (xx - 1) + yy]){//如果是更好的子路 
						vis[c * (xx - 1) + yy] = pth_min;
						q.push(path{xx, yy, pth_min});
					}
					break;
				}
				
				if(a[c * (xx - 1) + yy] == 1){//合法路径直接返回 
					ans = max(pth_min, ans);
					break;
				}
			}
		}
	}
}

main(void){
	input();
	
	BFS();
	
	solve_BFS();
	
	output();
}