1. 程式人生 > 其它 >Solution -「JOISC 2017」「LOJ #2392」煙花棒

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

  

\(\mathscr{Description}\)

  Link.

  有 \(n\) 個人站在數軸上,第從左往右第 \(i\) 個人的座標是 \(x_i\),每個人手上有一支菸花棒,每支菸花棒能燃燒 \(T\) 秒,燃盡後無法再點燃。初始時只有 \(k\) 手上的煙花棒是點燃的,求這些人的移動速度上限的最小值 \(v\in\mathbb N\),使得能夠用這支菸花棒薪火相傳,點燃所有煙花棒。

  \(n\le10^5\)

\(\mathscr{Solution}\)

  跪拜出題人系列。

  顯然二分答案,考慮如何檢查當前答案 \(v\) 的合法性。

  先得到一些有基礎性意義的貪心轉化:

  1. 任意時刻,只需要一支燃燒的煙花棒存在。

  2. 所有人會向燃著的煙花棒移動。

  3. 當一支剩餘燃燒時間為 \(t\) 的煙花棒點燃另一支菸花棒,等價於另一支菸花棒獲得 \(T+t\) 的燃燒時間,這支菸花棒立即燃盡。(轉化到原問題情景:兩個人一起跑,要燃盡時再傳火。)

  想像從 \(k\) 出發點燃煙花棒的情景,對於某個時刻,已經點燃過的煙花棒一定是一個區間 \([l,r]\),且 \(k\in[l,r]\)。根據性質 1,此時 \([l,r]\) 之外的人的相對位置是不變的,我們可以想象作區間 \([l,r]\) 被縮成了一個點。

  進一步,在這個條件下,“時刻”這一概念已經不重要了——無論何時,把 \(x_i\)

\(x_{i+1}\),縮在一起所需的時間都是 \(\frac{x_{i+1}-x_i}{2v}\)。而根據性質 3.,把這兩個座標縮起來,對煙花棒剩餘燃燒時間的影響也恆為 \(\Delta t_i=T-\frac{x_{i+1}-x_i}{2v}\)。記 \(P=\langle \Delta t_{k-1},\Delta t_{k-2},\cdots,\Delta t_1\rangle\)\(Q=\langle \Delta t_k,\Delta t_{k+1},\cdots,\Delta t_{n-1}\rangle\),問題轉化為:初始時有剩餘時間 \(t=T\),每次從序列 \(P\)
或者 \(Q\) 的開頭取出一個 \(\Delta t\),令 \(t\leftarrow t+\Delta t\),在保證 \(t\ge 0\) 的情況下將 \(P\)\(Q\) 刪空。

  嘗試貪心?唯一顯然的結論是若 \(P\) 的開頭或者 \(Q\) 的開頭不小於 \(0\),我們可以放心大膽取出來,而都小於 \(0\) 的情況就很難討論清楚了。當然,每次“涉險”取走負數後,我們又會立馬把露出來的非負數全部取走。我們能否規避“都小於 \(0\)”的討論,用已知結論描述完整的貪心過程?

  接下來這步,或說是構造,說起來平淡:我們“如果”能把 \(P\)\(Q\) 從開頭起,分為若干極短的段,每段描述為二元組 \((t_0,\Delta t)\),滿足 \(\Delta t\ge0\),表示為了消除這一段,初始時至少有 \(t_0\) 的剩餘燃燒時間,消除後獲得 \(\Delta t\) 的燃燒時間。由於劃分的極短性,每次必然消除完整的一段而不會半途而廢,不然穩虧不賺。

  這種“虛妄”有什麼用?如果“如果”不成立,不還是乾瞪眼?

  等等,如果“如果”不成立,例如 \(P\) 的“如果”不成立,那麼就存在 \(P\) 的一段極長字尾 \(P'\),滿足 \(P'\) 任意字首和為負數。我們引入《TENET》的宇宙觀,如果有一個逆熵的人從結束狀態開始貪心,他的初始時間 \(t\)——也就是我們的結束時間 \(t\),是確定的;他每次必須從 \(P\) 的末尾取數,當然 \(P\) 中的數也取反,那麼……

  極長字尾 \(P'\),取相反數再翻轉得到 \(P''\),滿足 \(P''\) 任意字尾和為正數……那麼 \(P''\) 就不存在任意字首和為負數的字尾……再者,\(P''\) 可以被劃分為上文中的若干極短段,也就是說,這個逆熵的人完全可以在“如果”的美好願景下做貪心,直到把 \(P'\) 消掉!

  然後呢然後呢?順熵的人把 \(P\) 消除剩下一個 \(P'\),逆熵的人把 \(P'\) 反著消除,兩個人相遇,我們作為順熵的人,靈機一動,逆著逆熵的人的行動構造方案,不就把 \(P\) 消除了嗎?

  神諭啊!

  實現上,可以把 \(\Delta t_i\) 轉化到勢差 \(h_{i+1}-h_i\) 上,所用東西乘上 \(2v\),構造 \(x_i\) 的勢 \(h_i=2vTi-x_i\),順帶把初始的 \(t=T\) 一塊兒囊括;在消去區間 \([l,r]\) 時保持 \(h_r\ge h_l\) 即可。演算法複雜度是 \(\mathcal O(n\log V)\) 的,其中 \(V\) 是一堆亂七八糟東西的值域上限。


  從神諭中體會到什麼?

  ——如果末狀態已知,那它何不可做初狀態?

  ——用時間的流向逆轉“死局”,讓它成為“必勝”條件。(好魔怔。

  出題人 nb。

\(\mathscr{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;
}