题目

点这里看题目。

分析

其实是一道比较套路的题目。一开始就并不那么容易想到如下的 DP:

\(f_{i,j}\) 表示第 \(i\) 次滋水时,当前若处在 \(j\) 位置,可能受到的最小伤害。转移还是比较显然:

\[f_{i,j}= \begin{cases} \min_{j-T_{i}+T_{i-1}\le k\le j+T_i-T_{i-1}}\{f_{i-1,k}\}+\max\{X_i-j,0\}&D_i=0\\ \min_{j-T_{i}+T_{i-1}\le k\le j+T_i-T_{i-1}}\{f_{i-1,k}\}+\max\{j - X_i,0\}&D_i=1\\ \end{cases} \]

两种情况只有 \(\max\) 部分不同,这取决于滋水的方向。可以发现这个转移形式还是比较简单的,只有两种需要维护的操作——区间取 \(\min\) 和全局叠加上一个分段函数。分段函数本身也很特别,是由一次函数和常函数拼接而成。

多手玩一下容易发现一个事实——如果将 \(j\) 看作自变量,那么 \(f_i\) 自己也是一个分段函数,并且其一定是由斜率为 \(-\infty,\dots,-3,-2,-1,0,1,2,3,\dots,+\infty\) 的若干段一次函数拼起来的。这样的话我们就可以分斜率正负来维护这些一次函数之间的断点。当然,这样 \(f_i\) 看起来会像是一个巨大的下凸壳,因此我们就称斜率为负的部分为左侧凸壳,正的部分为右侧凸壳。下面,我们需要使用数据结构分别维护左右凸壳,并且用变量 \(b\) 维护斜率为 0 的部分的值。

首先处理取 \(\min\) 操作。由于 \(f_{i-1}\) 是凸的,所以对于左侧凸壳,一定有 \(f_{i-1,j}\ge f_{i-1,j+1}\),右侧对称;因此这个取 \(\min\) 操作实际上是将左侧凸壳向左移动 \(T_i-T_{i-1}\),右侧凸壳向右移动 \(T_{i}-T_{i-1}\),中间斜率为 0 的部分就被拉宽了。整体平移容易操作,打标记就好。

这里 \(D_i=0\)\(D_i=1\) 本质上是完全对称的,我们只需要讨论 \(D_i=0\) 的情况。这相当将 \(f_i\) 加上一个类似于 \_ 形状的东西,而 \_ 上的断点即为 \(X_i\)。我们姑且称 \(g_j=\max\{X_i-j\}\)。设右侧凸壳上最靠左的一个点的横坐标为 \(r\),我们尝试维护 \(f_i\)

  • 在左侧凸壳中 \(j\le X_i\) 的部分中,对于斜率为 \(-k\) 的段,它的斜率会变成 \(-k-1\);由于我们维护的是 \(f_i\) 上的断点,因此在左侧直接插入 \(X_i\) 即可。
  • 在右侧凸壳中 \(j\le X_i\) 的部分中,对于斜率为 \(-k\) 的段,它的斜率会变成 \(-k+1\)。所以,如果 \(g\) 的叠加会影响到右侧凸壳,我们就需要更新 \(b\),并且将 \(r\) 从右侧删除,在右侧加入 \(X_i\)。可以发现,此时 \(r\) 一定会在斜率为 0 的段上,因此我们只需要计算 \(f_{i,r}\) 的变化量即可更新 \(b\),也即给 \(b\) 加上 \(X_i-r\)

这样,每次修改需要做的事情有:

  • 对左右凸壳分别打标记;
  • 可能需要取出左凸壳的最右点和右凸壳的最左点;

因此可以使用堆来维护。

最后还需要注意,\(f_0\) 的形式与我们所维护的分段函数不太相同——我们认为,它除了 \(f_{0,0}=0\) 之外,其余位置都为 \(+\infty\)。为了处理好这一点,我们可以先在左右凸壳中加入充分多个位于 0 的“断点”,那么除了 0 以外的位置就不会被计算到了。

小结:

  1. 仍然是开头就开得不好。虽然这个 DP 没什么难度,但是现场没有很快地反应过来想到这个 DP,甚至可以说是“觉得它不太可能所以想都没有想就叉掉了”。这其实是比较糟糕的习惯,不能放弃任何可能的思路
  2. 注意维护分段函数的方法:有时候我们可以维护差分,有时候我们可以维护断点
代码
#include <queue>
#include <cstdio>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef long long LL;

template <typename _T>
void read( _T &x )/*{{{*/
{
	x = 0; char s = getchar(); bool f = false;
	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	if( f ) x = -x;
}/*}}}*/

template <typename _T>
void write( _T x )/*{{{*/
{
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}/*}}}*/

std :: priority_queue<LL> lef;
std :: priority_queue<LL, std :: vector<LL>, std :: greater<LL> > rig;

int N;

int main()
{
	freopen( "data.in", "r", stdin );
	rep( i, 1, 1e6 ) lef.push( 0 ), rig.push( 0 );
	read( N );
	int lst = 0, T, D, X;
	LL tagL = 0, tagR = 0, ans = 0;
	while( N -- )
	{
		read( T ), read( D ), read( X );
		int delt = T - lst; lst = T;
		tagL -= delt, tagR += delt;
		if( D == 0 ) lef.push( X - tagL );
		else rig.push( X - tagR );
		if( lef.empty() || rig.empty() || 
			lef.top() + tagL <= rig.top() + tagR ) continue;
		LL l = lef.top() + tagL; lef.pop();
		LL r = rig.top() + tagR; rig.pop();
		if( D == 0 ) ans += X - r;
	 	else ans += l - X;
		lef.push( r - tagL ), rig.push( l - tagR );
	}
	write( ans ), putchar( '\n' );
	return 0;
}