• 「ABC217H」Snuketoon


    题目

    点这里看题目。

    分析

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

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

    [f_{i,j}= egin{cases} min_{j-T_{i}+T_{i-1}le kle 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 kle 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)

    • 在左侧凸壳中 (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 以外的位置就不会被计算到了。

    小结:

    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( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    计算机二进制总结
    java-集合排序,队列,散列表map以及如何遍历
    java-Collection,List简单使用与方法/(集合使用-中)
    java-Date类与集合(上)
    java-正则、object中的两个方法的使用
    java-注释、API之字符串(String)
    Java-面向对象三大特征、设计规则
    java-多态、内部类
    java-修饰词、抽象类、抽象方法
    java-重载、包修饰词以及堆栈管理
  • 原文地址:https://www.cnblogs.com/crashed/p/15229823.html
Copyright © 2020-2023  润新知