Codeforces周赛 #766 (Div. 2):E. Not Escaping(dp + vector离散压缩空间)

哼😕,想逃?

在这里插入图片描述
题意:

  • 给定一个 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 sumnsummsumk 的值不超过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;
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值