1. 程式人生 > 其它 >AtCoder ABC177 F - I hate Shortest Path Problem 題解

AtCoder ABC177 F - I hate Shortest Path Problem 題解

一道看似是迷宮的題目,但實際卻不像看起來那麼簡單。

Warning:
所有更新在原文釋出,在原文食用體驗更佳!

題意

題目連結:https://atcoder.jp/contests/abc177/tasks/abc177_f

一個 \((H + 1) \times W\) 的網格,每個位置都可以往右走一格(最右邊一列不行),第 \(i\) 行除了第 \(A_i\) 到第 \(B_i\) 個格子以外都可以往下走。
你可以從第 \(1\) 行任意一個格子開始走,對於每一個 \(i \in [2, n + 1]\),求走到第 \(i\) 行任意一個格子的最小步數。
其中 \(H, W \le 2 \times 10^5\)\(1 \le A_i \le B_i \le W\)

思路

貪心思想

容易發現,除了第 \(1\) 行,每個格子肯定是儘量往下走,即只有當不能往下走時才往右走。
那麼初步想法是進行 DP。

DP

下面用 \((i, j)\) 表示第 \(i\) 行第 \(j\) 列的格子。

\(f(i, j)\) 表示從第一行任意一個格子走到 \((i, j)\) 時的最小步數。
那麼對於 \(f(i, j)\) 有以下三種情況:

  1. 如果上面有擋板,則 \(f(i, j) = \inf\),可以證明這樣對答案沒有影響。
  2. 如果上面和左上(即 \((i - 1, j - 1)\))都沒有擋板,則 \(f(i, j) = f(i - 1, j) + 1\)
  3. 如果上面沒有擋板,左上有擋板,則 \(f(i, j) = \min_{k = 1}^j f(i - 1, k) + 1\)

其中從左邊往右邊走的貢獻全部集中在了第三種轉移。

上面的轉移即:

\[f(i, j) = \begin{cases} 0&(i=1)\\ \inf&(A_{i - 1} \le j \le B_{i - 1})\\ f(i - 1, j) + 1&(B_{i - 1} < j - 1 \;\texttt{or}\; A_{i - 1} > j)\\ \min_{k = 1}^j f(i - 1, k) + 1&(B_{i - 1} = j - 1) \end{cases} \]

資料結構優化

但是上面 DP 的複雜度是 \(O(HW)\)

的,在這道題肯定是過不去的。這時候我們可以用線段樹優化。

我們對於每個 \(f(i, \cdots)\) 用線段樹維護,直接 \(O(\log H)\) 進行維護。(類似揹包的思想,直接在原線段樹上進行更改)

對於第一種轉移,區間賦值/區間加 \(\inf\)
對於第二種轉移,區間加 \(1\)
對於第三種轉移,需要再維護一個線段樹,值為 \(f(i, j) + W - j\)。此時第三種轉移就用第二個線段樹的值來維護。

程式碼

#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5 + 5;
const int INF = 0x3f3f3f3f;

typedef long long LL;

int a[N], b[N];
int n, m;

struct SegTree {
    LL t[N << 2], ladd[N << 2];
    inline void lazy_down(int x) {
        t[x << 1] += ladd[x];
        t[x << 1 | 1] += ladd[x];
        ladd[x << 1] += ladd[x];
        ladd[x << 1 | 1] += ladd[x];
        ladd[x] = 0;
    }
    void modify_add(int ql, int qr, LL qadd, int x = 1, int l = 1, int r = m) {
        if(ql <= l && r <= qr) { t[x] += qadd, ladd[x] += qadd; return; }
        int mid = l + ((r - l) >> 1);
        lazy_down(x);
        if(ql <= mid) modify_add(ql, qr, qadd, x << 1, l, mid);
        if(qr > mid) modify_add(ql, qr, qadd, x << 1 | 1, mid + 1, r);
        t[x] = min(t[x << 1], t[x << 1 | 1]);
    }
    LL query_min(int ql, int qr, int x = 1, int l = 1, int r = m) {
        if(ql <= l && r <= qr) return t[x];
        int mid = l + ((r - l) >> 1);
        lazy_down(x);
        if(qr <= mid) return query_min(ql, qr, x << 1, l, mid);
        else if(ql > mid) return query_min(ql, qr, x << 1 | 1, mid + 1, r);
        else return min(query_min(ql, qr, x << 1, l, mid), query_min(ql, qr, x << 1 | 1, mid + 1, r));
    }
};

SegTree ans1; // f[][i]
SegTree ans2; // f[][i] + m - i

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]);
    for(int i = 1; i <= m; i++) ans2.modify_add(i, i, m - i);
    for(int i = 1; i <= n; i++) {
        LL tmp = ans2.query_min(1, b[i] + 1) - ((LL)m - b[i] - 1);
        ans1.modify_add(b[i] + 1, b[i] + 1, min(tmp - ans1.query_min(b[i] + 1, b[i] + 1), 0LL));
        ans2.modify_add(b[i] + 1, b[i] + 1, min(tmp + m - (b[i] + 1) - ans2.query_min(b[i] + 1, b[i] + 1), 0LL));
        ans1.modify_add(a[i], b[i], INF);
        ans2.modify_add(a[i], b[i], INF);
        ans1.modify_add(1, m, 1);
        ans2.modify_add(1, m, 1);
        tmp = ans1.query_min(1, m);
        if(tmp >= INF) printf("-1\n");
        else printf("%lld\n", tmp);
    }
    return 0;
}