前言
題目連結: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;
}