哼😕,想逃?
题意:
-
给定一个 N * M 的图,柯笔需要从 {1,1} 点跑到 {n,m} 点坐上直升飞机逃生(然后吃上他最爱的小布丁😋),否则就会在燃烧的大楼里被加工成焦🖊,世纪大危机!
-
给你 k 个建设在大楼中的楼梯,起点是{ai,bi},终点是{ci,di},注意这里的 ai、ci 才是 N 轴上的!(
不然整个就会理解大错) ,不会有重叠的楼梯,楼梯是单向的,柯笔通过楼梯一定从低往高走,每个楼梯能有一个小布丁恢复柯笔 hi 点烧焦值; -
在大楼中竖向走只能通过楼梯,横向走是很危险的,一不留神就会被烧焦!定义在第 i 层中横向走会得到 x[i] * (横向走的位移距离) 点烧焦值;
-
问:柯笔最后能不能逃生?如果会被烧焦输出“NO ESCAPE”,否则输出柯笔的最小烧焦值
思路:
-
图中总点数有 N * M <= 1e10 个,可以发现有很多点是没必要记录的。我们只需要记录每个楼梯的两端点和起点终点即可(2 * k + 2 <= 2e5 + 2)
-
对于每一层,显然低层被维护好了之后才能更新高层,所以最外层循环确定;
-
对于同层的离散后的点,它们彼此之间可以相互维护,即通过横向走维护一个最优 dp[i]。但这些点是散乱无序的,用枚举的话时间会炸,可以发现用 vector 离散过后所有层的点 加起来 个数最多才 2e5 + 2 个,所以每一层都排序的时间是允许的
-
我们对同层点根据横坐标从小到大排序,左到右、右到左遍历两次维护 dp[i],维护好当前层后,再对于该层的楼梯起点,更新楼梯终点的dp值即可
注意数组清空、初始化问题,还有边界问题!
题目保证了 t 组样例中的 s u m n , s u m m , s u m k sum_n,sum_m,sum_k sumn,summ,sumk 的值不超过1e5,因此每组数据用了哪些空间就清空哪些,不要用memset,组数一多就会超时
代码:
#include<bits/stdc++.h>
#include<unordered_set>
#include<unordered_map>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define ul (u << 1)
#define ur (u << 1 | 1)
#define x first
#define y second
//#pragma GCC optimize(2)
//[博客地址](https://blog.csdn.net/weixin_51797626?t=1)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef pair <ll, PII> PI;
const int N = 100010, M = 200010, MM = N;
int INF = 0x3f3f3f3f, mod = 100003;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
int x[N];
vector<PII> row[N];//每行离散后的点
ll dp[M];//离散后的点维护的最小烧焦值
PII to[M];//记录每个楼梯起点通向的终点位置和恢复值
int main() {
cinios;
cin >> T;
while (T--)
{
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) { //用到多少就清空多少
cin >> x[i];
row[i].clear();
}
for (int i = 1; i <= 2 * k + 3; i++) { //如果用mem清空,多组输入过大会超时
dp[i] = LNF;
to[i] = { 0,0 };//to有用来判断,不要忘记初始化
}
int cnt = 0, a, b, c, d, h, siz;
dp[1] = 0;//起点
row[1].push_back({ 1,++cnt });
while (k--)
{
cin >> a >> b >> c >> d >> h;
row[a].push_back({ b,++cnt });//存入 + 离散
row[c].push_back({ d,++cnt });
to[cnt - 1] = { cnt,-h };//记录任意楼梯 起点 到 终点 的信息
}
row[n].push_back({ m,++cnt });//不要忘记放入终点
for (int i = 1; i <= n; i++) {
sort(row[i].begin(), row[i].end());//默认按照PII第一个关键值排序,也就是横坐标
siz = row[i].size();
//x、y重定义了first、second
for (int j = 1; j < siz; j++)
dp[row[i][j].y] = min(dp[row[i][j].y], dp[row[i][j - 1].y] + 1LL * x[i] * (row[i][j].x - row[i][j - 1].x));
//右边由左边一路dp维护过来
for (int j = siz - 2; j >= 0; j--)
dp[row[i][j].y] = min(dp[row[i][j].y], dp[row[i][j + 1].y] + 1LL * x[i] * (row[i][j + 1].x - row[i][j].x));
//左边由右边一路维护过来
for (auto j : row[i])//维护完本层后再更新 本层楼梯起点能抵达的终点的dp值
if (to[j.y].x && dp[j.y] != LNF)//注意特判dp[j.y] != LNF,因为to信息有可能为负值,即使不合法也能更新成功
dp[to[j.y].x] = min(dp[to[j.y].x], dp[j.y] + to[j.y].y);
}
if (dp[cnt] == LNF)cout << "NO ESCAPE";//1😕 23? 45678!
else cout << dp[cnt];//终点的离散值为cnt
cout << '\n';
}
return 0;
}