Snuketoon [ABC217H]

https://atcoder.jp/contests/abc217/tasks/abc217_h

题解

\(f_{i,x}\) 表示在 \(T_i\) 时刻在 \(x\) 位置,受到的最小伤害是多少

\(m_i = T_i-T_{i-1}\)

\(D_i=0\) 为例,转移有 \(f_{i,x}=\min\limits_{y\in [x-m_i,x+m_i]} f_{i-1,y} + \min\{X_i-x,0\}\)

观察一下样例可以发现,对于一个固定的 \(i\) ,若把所有 \((x,f_{i,x})\) 当成点画在二维平面上并连接相邻点,那么图像会是一段下凸壳,也就是说 \(f_{i,x}\) 是一个下凸的函数

不妨把上面的转移分成两步,先是 \(f_{i,x}=\min\limits_{y\in [x-m_i,x+m_i]} f_{i-1,y}\)

此时对于凸壳左半边斜率小于 \(0\) 的部分,有 \(f_{i,x-1} > f_{i,x}\) ,所以 \(f_{i,x}=\min\limits_{y\in [x-m_i,x+m_i]} f_{i-1,y}=f_{i-1,x+m_i}\)

同理对于右半边斜率大于 \(0\) 的部分,有 \(f_{i,x}=f_{i-1,x-m_i}\)

所以这一步就相当于将凸壳左半边向左平移 \(m_i\) ,右半边向右平移 \(m_i\)

第二步是给每个 \(f_{i,x}\) 加上 \(\min\{X_i-x,0\}\) (仍然以 \(D_i=0\) 为例)

可以发现,这个操作相当于给 \([-\infty,X_i]\) 这段的 \(f_{i,x}\) 加上一个斜率为 \(-1\) 的一次函数

考虑怎么维护这个凸壳:

对于左半边斜率小于0的部分,从右到左维护若干个断点 \(p_1,p_2,\dots,p_k\),表示 \([p_1,p_2]\) 区间上凸壳斜率为 \(-1\)\([p_2,p_3]\) 上斜率为 \(-2\),......

对于右半边同理;额外维护一个 \(ans\) 表示斜率为 \(0\) 部分的答案

对于第一步转移,直接维护左半边和右半边的偏移量即可

对于第二步转移,以 \(D_i=0\) 为例,设右半边凸壳的第一个断点为 \(r\)

如果 \(X_i \le r\) ,那么此时 \(X_i\) 左边的部分斜率要全部 \(-1\) ,所以将 \(X_i\) 作为断点加入左半边凸壳的断点集合即可

如果 \(X_i > r\) ,那么有一段原来斜率为 \(1\) 的区间加上这个斜率为 \(-1\) 的一次函数后斜率变成 \(0\) 了,更新 \(ans+=X_i-r\),同时将右半边第一个断点改为 \(X_i\) ,左半边新增了断点 \(r\)

下面放一张图方便理解:

\(D_i=1\) 时也同理

最后答案即为 \(ans\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int n;
priority_queue<ll> q0;
priority_queue<ll, vector<ll> , greater<ll> > q1;

int main() {
	scanf("%d", &n); ll ans = 0;
	for (int i = 1, t, d, x; i <= n; i++) {
		scanf("%d %d %d", &t, &d, &x);
		if (d == 0) {
			if (x > t) ans += x-t, x = t;
			if (q1.empty() || x <= q1.top()+t) q0.push(x+t);
			else {
				ll y = q1.top()+t;
				ans += x-y;
				q0.push(y+t); 
				q1.pop(); q1.push(x-t);
			}
		} else {
			if (x < -t) ans += -t-x, x = -t;
			if (q0.empty() || x >= q0.top()-t) q1.push(x-t);
			else {
				ll y = q0.top()-t;
				ans += y-x;
				q0.pop(); q0.push(x+t);
				q1.push(y-t);
			}
		}
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2021-10-29 12:36  AK_DREAM  阅读(169)  评论(0编辑  收藏  举报