[POI2014] RAJ-Rally 題解

XuYueming發表於2024-09-04

前言

題目連結:Hydro & bzoj黑暗爆炸洛谷

題意簡述

DAG 求刪點後最長路的最小值。

\(n \leq 5 \times 10 ^ 5\)\(m \leq 10^6\)

題目分析

其實對於刪點 / 邊加查詢最長 / 短路的套路是有的。比如:故鄉的夢。本題也類似。

我們考慮,如果刪除的邊不在原來最長路上,那麼刪之後的圖的最長路不變。又因為我們一定可以刪除最長路上的一條邊來得到不劣的答案,所以,我們刪除就只會刪除原來最長路上的某一個點。如果最長路不唯一,任取不影響正確性。

考慮最長路 \(d_1 \ldots d_k\),設刪除的點為 \(u = d_{p}\)。那麼刪除後的最長路可能是 \(d_1 \ldots d_{p - 1}\)\(d_{p + 1} \ldots d_k\),也可能是其他不經過點 \(u\) 的最長路。這麼看似乎沒什麼頭緒,我們稍微轉化一下。對於原圖的任意最長路,其起點入度和終點出度必為 \(0\),所以套路地,我們用一個超級源點 \(S\) 連向所有入度為 \(0\) 的點,所有出度為 \(0\) 的點連向超級匯點 \(T\)。這樣,我們發現最長路 \(d_1 = S\)\(d_k = T\)。以及,刪點後的最長路一定形如:\(S \rightarrow d_{i} \rightarrow x \rightarrow y \rightarrow d_{j} \rightarrow T\),其中 \(i \lt p\) 並且 \(j \gt p\)。那麼 \(d_i\) 就是以 \(S\) 為根的最長路徑樹上,\(x\) 到鏈 \(S \sim T\) 的結點,\(d_j\) 是以 \(T\) 為根的反圖的最長路徑樹上, \(y\) 到鏈 \(S \sim T\) 的結點。這是由於,我們需要使得 \(S \rightarrow x\)\(y \rightarrow T\) 是最長路。

用 BFS 或 DFS 什麼的標記一下預處理很方便。不妨把 \((x, y)\) 這條非樹邊掛在 \(d_i\) 上,記作二元組 \(v_i = (d_j, len)\),其中 \(len\) 是經過 \((x, y)\) 最長路長度。查詢變成了在 \(v_{1 \sim p - 1}\) 中,前者 \(\gt p\) 的後者的最小值。

按照 \(p = 1 \ldots k\) 的順序掃過去,對於一個 \(d_i = p\) 的二元組,在一個資料結構的 \(d_j\) 位置取 \(\max \{ len \}\)。對於 \(p \in [2, k - 1]\),我們查詢刪除這個點的答案,就是在 \(i - 1\) 操作後的基礎上查詢 \(i + 1 \sim k\) 的最大值。樹狀陣列維護即可。

時間複雜度:\(\Theta((n + m) \log n)\),瓶頸在於樹狀陣列單點修改,字尾查最值。

程式碼

#include <cstdio>
#include <iostream>
using namespace std;

constexpr const int MAX = 1 << 27, yzh_i_love_you = 1314520736;
char buf[MAX], *p = buf;
#define getchar() (*p++)
#define isdigit(x) ('0' <= x && x <= '9')
inline void read(int &x) {
    x = 0; char c = 0;
    for (;!isdigit(c); c = getchar());
    for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
}

const int N = 500010;
const int M = 1000010;

int n, m;

int line[N], whr[N], tim;
bool inpath[M];

struct Graph {
    struct node {
        int to, nxt;
    } edge[M + N * 2];
    int head[N], du[N], tot;
    inline void add(int u, int v) {
        edge[++tot] = {v, head[u]};
        head[u] = tot, ++du[v];
    }
    inline node & operator [] (int x) {
        return edge[x];
    }
    int dis[N], fr[N], bl[N];
    inline void solve(int root) {
        static int Q[N], top(0);
        Q[++top] = root, dis[root] = -1;
        while (top) {
            int now = Q[top--];
            for (int i = head[now]; i; i = edge[i].nxt) {
                int to = edge[i].to;
                if (dis[now] + 1 >= dis[to]) {
                    dis[to] = dis[now] + 1;
                    fr[to] = i;
                }
                if (!--du[to]) Q[++top] = to;
            }
        }
    }
    void mark(int s) {
        static int Q[N], top(0);
        Q[++top] = s;
        bl[s] = s;
        while (top) {
            int now = Q[top--];
            for (int i = head[now]; i; i = edge[i].nxt) {
                int to = edge[i].to;
                if (fr[to] != i) continue;
                bl[to] = inpath[i] ? to : bl[now];
                Q[++top] = to;
            }
        }
    }
} xym, yzh;

struct Trans {
    struct node {
        int to, len, nxt;
    } edge[M];
    int tot, head[N];
    inline void add(int u, int v, int w) {
        edge[++tot] = {v, w, head[u]};
        head[u] = tot;
    }
    inline node & operator [] (int x) {
        return edge[x];
    }
} trans;

void getpath() {
    int T = 1, S;
    for (int i = 1; i <= n; ++i) if (xym.dis[i] > xym.dis[T]) T = i;
    for (S = T; xym.fr[S]; S = yzh[xym.fr[S]].to);
    for (int i = S; ; i = xym[yzh.fr[i]].to) {
        line[++tim] = i;
        whr[i] = tim;
        if (!yzh.fr[i]) break;
        inpath[yzh.fr[i]] = true;
    }
}

struct Bit_Tree {
    int tree[N];
    inline int lowbit(int x) {
        return x & -x;
    }
    inline void modify(int p, int v) {
        for (int i = p; i; i -= lowbit(i)) tree[i] = max(tree[i], v);
    }
    inline int query(int p) {
        int res = 0;
        for (int i = p; i <= tim; i += lowbit(i)) res = max(res, tree[i]);
        return res;
    }
} tree;

signed main() {
    fread(buf, 1, MAX, stdin);
    read(n), read(m);
    for (int i = 1, u, v; i <= m; ++i) {
        read(u), read(v);
        xym.add(u, v);
        yzh.add(v, u);
    }
    for (int i = 1; i <= n; ++i) {
        if (!xym.du[i]) xym.add(0, i), yzh.add(i, 0);
        if (!yzh.du[i]) xym.add(i, n + 1), yzh.add(n + 1, i);
    }
    xym.solve(0), yzh.solve(n + 1);
    getpath();
    xym.mark(0), yzh.mark(n + 1);
    for (int u = 1; u <= n; ++u)
        for (int i = xym.head[u]; i; i = xym[i].nxt) {
            int v = xym[i].to;
            if (xym.fr[v] == i) continue;
            if (whr[xym.bl[u]] + 1 >= whr[yzh.bl[v]]) continue;
            trans.add(whr[xym.bl[u]], whr[yzh.bl[v]], xym.dis[u] + 1 + yzh.dis[v]);
        }
    int ans = 0x3f3f3f3f, u = -1;
    for (int i = 1; i <= tim - 1; ++i) {
        if (i >= 2) {
            int res = tree.query(i + 1);
            res = max(res, xym.dis[line[i - 1]]);
            res = max(res, yzh.dis[line[i + 1]]);
            if (res < ans) ans = res, u = line[i];
        }
        for (int j = trans.head[i]; j; j = trans[j].nxt)
            tree.modify(trans[j].to, trans[j].len);
	}
    printf("%d %d", u, ans);
    return 0;
}