「JOISC2017」烟花棒

题目

点这里看题目。

题目大意:

数轴上有 \(n\) 个人,每个人手上拿着一根烟花棒。按照坐标从小到大,\(n\) 个人被编号为 \(1\sim n\),且编号为 \(i\) 的人的坐标为 \(X_i\);保证 \(X_1=0\)

从某一时刻开始,编号为 \(k\) 的人的烟花棒被点燃。从那一时刻起,所有人可以开始任意跑动——即,任意时刻他们都可以选择向某个方向移动,或者静止不动。

一根烟花棒可以恰好连续燃烧 \(T\) 秒。若在某一时刻,手持点燃的烟花棒的人与另一手持未点燃烟花棒的人处于同一坐标,未点燃的烟花棒就可以被点燃。除了通过前述方式之外,除了 \(k\) 的烟花棒外其他人的烟花棒都无法被点燃,并且一根被点燃过的烟花棒(无论熄灭与否)都无法再被点燃

现在,请求出为了让所有烟花棒都被点燃过(可以有一些熄灭了),人的跑动速度的最大值。这应当是一个非负整数

对于 \(100\%\) 的数据,满足 \(1\le k\le n\le 10^5,1\le T\le 10^9,0\le X_i\le 10^9\),且保证 \(X_1=0,\{X_n\}\) 单调递增。

分析

奇妙的题目。

首先,二分答案,尝试检查一个速度的最大值 \(v\) 是否合法。此时,由于时间是连续的,因而我们只需要考虑距离,而大可忽略“时间”的概念。

下面有一些“简单”的观察:

  • 所有人都会朝着当前有火的人跑。

    比较显然。瞎跑会浪费不必要的时间,尤其是发现下面一点时。

  • 任意时刻,全场只需要一个有火的人

    注意到:如果有火的人遇到了没火的,那么不必着急去点火。没火的可以先以相同速度陪跑,等到快要熄灭时再续命。

    此外,可以证明,在没熄灭时点火分开跑,和陪跑续命的策略,本质上是效果相同的。此处就略过了。

  • 每遇到一个没火的人,就相当于给当前的火续了 \(T\) 秒的命。

    这个根据上一条是显然的。更进一步地,由于两个人要么相向而行,要么并向而行,这相当于是给烟花提供了 \(2vT\) 的移动距离

  • 持有火的人只有在遇到了一个没有火的人的时候,才会掉头。

那么,现在遇到一个人,实际上就是把他“吃掉”,然后给烟花续命,所以总认为只有 \(k\) 持有点燃的烟花。考虑 \(k\) “吃” \(k-1\) 的烟花,则编号更小的人会向 \(k\) 移动,而编号更大的人,相对来说,到 \(k\) 的距离不变。这就意味着,每当 \(k\) 要“吃” \(i\) 的烟花的时候,由于此时两者之间不可能有第三个人(不然就不优嘛),两者间的距离是预先确定的,也就是 \(X\) 的前向/后向差分。至于究竟是前向还是后向,取决于 \(k\)\(i\) 的大小关系。

所以,我们可以将 \(k\) 左侧和 \(k\) 右侧抽象为两个队列,以离 \(k\) 近的一端为队头。每次可以取出一个队列的头,消耗一定的距离“吃掉”他,并获得距离奖励。贪心地想,如果某个队列有一个极短的前缀,使得我们吃掉这个前缀后,恰好不会减少距离,且吃掉任意更短的前缀都只能减少距离,则通过调整,我们一定会连续地吃完这一整个前缀,而不会吃几口又去吃另一边——我们称之为“白嫖组”。这样的话,如果两个队列都可以完整地划分为若干个“白嫖组”,我们只需要按照“白嫖组”,每次选需要距离更少的一个来贪心就好了。

如果不行,我们仍然可以先把“白嫖组”吃完。只不过,剩下的队列一定满足任意一个前缀的奖励都少于它的消耗。下面的观察非常高妙:为了把问题转换到前面的情形,我们尝试反向考虑整个问题。注意到我们吃完整个队列的终止状态是确定的,所以我们可以尝试倒过来贪心:按顺序,遇到一个人就把得到的奖励“吐”出来,再加上之前的消耗。这个和之前的正向贪心是完全对称的。而更加“凑巧”的是,根据已有的性质,我们发现整个剩余队列的奖励少于消耗,因而在逆向贪心中,至少整个剩余队列可以成为“白嫖组”(可能不是极短的)。也就是说,按照正向贪心来就可以了!

这就是“时间钳形战术”,当 \(k\) 吃完所有前缀“白嫖组”之后,他和逆向的 \(k'\) 回师,然后交流之后大呼“高妙”,并按照 \(k'\) 的智慧决策构造出了自己的决策。

最终,模拟这个过程我们可以得到一个 \(O(n\log V)\) 的高妙算法。

小结:

  1. 仍然强调最基本的观察,更需要提醒的是,思路不要太死板了。有一些小的构造技巧,如果没想到就真的很难办。比如这里的“续命”策略,如果没有想到“陪跑”就很难拓展到这一步。

  2. 这里的贪心划分方式可以学习一下。

  3. 一个很好的例子:当我们知道了终止状态,并且需要知道过程可以不可以实现的时候,正反构造都是可以考虑的

代码

#include <deque>
#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;

const int MAXN = 1e5 + 5;

template<typename _T>
void read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { 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' );
}

template<typename _T>
_T Max( const _T a, const _T b ) {
    return a > b ? a : b;
}

template<typename _T>
_T Min( const _T a, const _T b ) {
    return a < b ? a : b;
}

typedef std :: pair<LL, LL> Clique;

std :: deque<Clique> pre, nxt;

int X[MAXN];

int N, K, T;

bool Simulate( LL ener ) {
    static auto Comp = [] ( const Clique &a, const Clique &b ) -> bool {
        return a.first == b.first ? a.second > b.second : a.first < b.first;
    };

    while( ! pre.empty() || ! nxt.empty() ) {
        Clique bst;
        if( ! pre.empty() && ( nxt.empty() || Comp( pre.front(), nxt.front() ) ) )
            bst = pre.front(), pre.pop_front();
        else bst = nxt.front(), nxt.pop_front();
        if( ener < bst.first ) return false;
        ener += bst.second;
    }
    return true;
}

bool Chk( const int veloc ) {
    pre.clear(), nxt.clear();
    LL add = 0, sub = 0, ned = 0, tim = 2ll * veloc * T;
    per( i, K - 1, 1 ) {
        ned = Max( ned, X[i + 1] - X[i] - ( add - sub ) );
        sub += X[i + 1] - X[i], add += 2ll * veloc * T;
        tim += 2ll * veloc * T - ( X[i + 1] - X[i] );
        if( add >= sub ) {
            pre.push_back( { ned, add - sub } );
            add = sub = ned = 0;
        }
    }
    add = sub = ned = 0;
    rep( i, K + 1, N ) {
        ned = Max( ned, X[i] - X[i - 1] - ( add - sub ) );
        sub += X[i] - X[i - 1], add += 2ll * veloc * T;
        tim += 2ll * veloc * T - ( X[i] - X[i - 1] );
        if( add >= sub ) {
            nxt.push_back( { ned, add - sub } );
            add = sub = ned = 0;
        }
    }
    if( ! Simulate( 2ll * T * veloc ) ) return false;
    pre.clear(), nxt.clear();
    add = sub = ned = 0;
    rep( i, 1, K - 1 ) {
        ned = Max( ned, 2ll * T * veloc - ( add - sub ) );
        sub += 2ll * T * veloc, add += X[i + 1] - X[i];
        if( add >= sub ) {
            pre.push_back( { ned, add - sub } );
            add = sub = ned = 0;
        }
    }
    add = sub = ned = 0;
    per( i, N, K + 1 ) {
        ned = Max( ned, 2ll * T * veloc - ( add - sub ) );
        sub += 2ll * T * veloc, add += X[i] - X[i - 1];
        if( add >= sub ) {
            nxt.push_back( { ned, add - sub } );
            add = sub = ned = 0;
        }
    }
    if( ! Simulate( tim ) ) return false;
    return true;
}

int main() {
    read( N ), read( K ), read( T );
    rep( i, 1, N ) read( X[i] );
    int l = 0, r = 1e9, mid;
    while( l < r ) {
        mid = ( l + r ) >> 1;
        if( Chk( mid ) ) r = mid;
        else l = mid + 1;
    }
    write( l ), putchar( '\n' );
    return 0;
}
posted @ 2022-03-30 21:57  crashed  阅读(127)  评论(0编辑  收藏  举报