题目传送门
先警示后人:
我在看题目的时候看成了 \(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();
}