Live2D

Solution -「JOISC 2017」「LOJ #2392」烟花棒

Description

  Link.

  有 n 个人站在数轴上,第从左往右第 i 个人的坐标是 xi,每个人手上有一支烟花棒,每支烟花棒能燃烧 T 秒,燃尽后无法再点燃。初始时只有 k 手上的烟花棒是点燃的,求这些人的移动速度上限的最小值 vN,使得能够用这支烟花棒薪火相传,点燃所有烟花棒。

  n105

Solution

  跪拜出题人系列。

  显然二分答案,考虑如何检查当前答案 v 的合法性。

  先得到一些有基础性意义的贪心转化:

  1. 任意时刻,只需要一支燃烧的烟花棒存在。

  2. 所有人会向燃着的烟花棒移动。

  3. 当一支剩余燃烧时间为 t 的烟花棒点燃另一支烟花棒,等价于另一支烟花棒获得 T+t 的燃烧时间,这支烟花棒立即燃尽。(转化到原问题情景:两个人一起跑,要燃尽时再传火。)

  想像从 k 出发点燃烟花棒的情景,对于某个时刻,已经点燃过的烟花棒一定是一个区间 [l,r],且 k[l,r]。根据性质 1,此时 [l,r] 之外的人的相对位置是不变的,我们可以想象作区间 [l,r] 被缩成了一个点。

  进一步,在这个条件下,“时刻”这一概念已经不重要了——无论何时,把 xixi+1,缩在一起所需的时间都是 xi+1xi2v。而根据性质 3.,把这两个坐标缩起来,对烟花棒剩余燃烧时间的影响也恒为 Δti=xi+1xi2v。记 P=Δtk1,T,Δtk2,T,,Δt1,TQ=Δtk,T,Δtk+1,T,,Δtn1,T,问题转化为:初始时有剩余时间 t=T,每次从序列 P 或者 Q 的开头取出一个 Δt,令 tt+Δt,在保证 t0 的情况下将 PQ 删空。

  尝试贪心?唯一显然的结论是若 P 的开头或者 Q 的开头不小于 0,我们可以放心大胆取出来,而都小于 0 的情况就很难讨论清楚了。当然,每次“涉险”取走负数后,我们又会立马把露出来的非负数全部取走。我们能否规避“都小于 0”的讨论,用已知结论描述完整的贪心过程?

  接下来这步,或说是构造,说起来平淡:我们“如果”能把 PQ 从开头起,分为若干极短的段,每段描述为二元组 (t0,Δt),满足 Δt0,表示为了消除这一段,初始时至少有 t0 的剩余燃烧时间,消除后获得 Δt 的燃烧时间。由于划分的极短性,每次必然消除完整的一段而不会半途而废,不然稳亏不赚。

  这种“虚妄”有什么用?如果“如果”不成立,不还是干瞪眼?

  等等,如果“如果”不成立,例如 P 的“如果”不成立,那么就存在 P 的一段极长后缀 P,满足 P 任意前缀和为负数。我们引入《TENET》的宇宙观,如果有一个逆熵的人从结束状态开始贪心,他的初始时间 t——也就是我们的结束时间 t,是确定的;他每次必须从 P 的末尾取数,当然 P 中的数也取反,那么……

  极长后缀 P,取相反数再翻转得到 P,满足 P 任意后缀和为正数……那么 P 就不存在任意前缀和为负数的后缀……再者,P 可以被划分为上文中的若干极短段,也就是说,这个逆熵的人完全可以在“如果”的美好愿景下做贪心,直到把 P 消掉!

  然后呢然后呢?顺熵的人把 P 消除剩下一个 P,逆熵的人把 P 反着消除,两个人相遇,我们作为顺熵的人,灵机一动,逆着逆熵的人的行动构造方案,不就把 P 消除了吗?

  神谕啊!

  实现上,可以把 Δti 转化到势差 hi+1hi 上,所用东西乘上 2v,构造 xi 的势 hi=2vTixi,顺带把初始的 t=T 一块儿囊括;在消去区间 [l,r] 时保持 hrhl 即可。算法复杂度是 O(nlogV) 的,其中 V 是一堆乱七八糟东西的值域上限。


  从神谕中体会到什么?

  ——如果末状态已知,那它何不可做初状态?

  ——用时间的流向逆转“死局”,让它成为“必胜”条件。(好魔怔。

  出题人 nb。

Code

/*+Rainybunny+*/

#include <bits/stdc++.h>

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

typedef long long LL;

const int MAXN = 1e5;
int n, st, T, x[MAXN + 5];
LL hgt[MAXN + 5];

inline bool check(const int v) {
    rep (i, 1, n) hgt[i] = 2ll * v * T * i - x[i];
    if (hgt[1] > hgt[n]) return false;
    int tarL = st, tarR = st;
    per (i, st - 1, 1) if (hgt[i] <= hgt[tarL]) tarL = i;
    rep (i, st + 1, n) if (hgt[i] >= hgt[tarR]) tarR = i;
    int l = st, r = st;
    while (tarL < l || r < tarR) {
        int pl = l, pr = r;
        for (int i = pl - 1; i >= tarL && hgt[i] <= hgt[pr]; --i) {
            if (hgt[i] <= hgt[pl]) pl = i;
        }
        for (int i = pr + 1; i <= tarR && hgt[i] >= hgt[pl]; ++i) {
            if (hgt[i] >= hgt[pr]) pr = i;
        }
        if (pl == l && pr == r) return false;
        l = pl, r = pr;
    }

    l = 1, r = n;
    while (l < tarL || tarR < r) {
        int pl = l, pr = r;
        for (int i = pl + 1; i <= tarL && hgt[i] <= hgt[pr]; ++i) {
            if (hgt[i] <= hgt[pl]) pl = i;
        }
        for (int i = pr - 1; i >= tarR && hgt[i] >= hgt[pl]; --i) {
            if (hgt[i] >= hgt[pr]) pr = i;
        }
        if (pl == l && pr == r) return false;
        l = pl, r = pr;
    }
    return true;
}

int main() {
    scanf("%d %d %d", &n, &st, &T);
    rep (i, 1, n) scanf("%d", &x[i]);

    int l = 0, r = 1e9;
    while (l < r) {
        int mid = l + r >> 1;
        // printf("%d?\n", mid);
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    printf("%d\n", l);
    return 0;
}

posted @   Rainybunny  阅读(155)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示