题目
点这里看题目。
分析
其实是一道比较套路的题目。一开始就并不那么容易想到如下的 DP:
设 (f_{i,j}) 表示第 (i) 次滋水时,当前若处在 (j) 位置,可能受到的最小伤害。转移还是比较显然:
两种情况只有 (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):
- 在左侧凸壳中 (jle X_i) 的部分中,对于斜率为 (-k) 的段,它的斜率会变成 (-k-1);由于我们维护的是 (f_i) 上的断点,因此在左侧直接插入 (X_i) 即可。
- 在右侧凸壳中 (jle 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 以外的位置就不会被计算到了。
小结:
- 仍然是开头就开得不好。虽然这个 DP 没什么难度,但是现场没有很快地反应过来想到这个 DP,甚至可以说是“觉得它不太可能所以想都没有想就叉掉了”。这其实是比较糟糕的习惯,不能放弃任何可能的思路!
- 注意维护分段函数的方法:有时候我们可以维护差分,有时候我们可以维护断点。
代码
#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( '
' );
return 0;
}