图论学习笔记
最短路
割边最短路
约定
\(1 \rightsquigarrow x\) 表示 \(1\) 到 \(x\) 的路径。
\(T(1 \rightsquigarrow x)\) 表示最短路树 \(T\) 上 \(1 \rightsquigarrow x\) 的经过的边的集合。
思路
问题:给定一张无向正权图,对图上的每条边,求删去该边后 \(1 \rightsquigarrow n\) 的最短路。
首先求出 \(1 \rightsquigarrow n\) 的最短路的路径 \(P\),定义 \(len\) 表示 \(P\) 包含边的数量,从 \(1 \sim len\) 对 \(P\) 的每一条边进行编号。设 \(e\) 表示当前考虑的边 \((i, j)\)。
若 \(e \notin P\),删去 \(e\) 后对答案没有影响。
若 \(e \in P\),考虑从 \(1\) 出发 和 到达 \(n\) 的最短路树 \(T_1\) 和 \(T_n\),要求 \(T_1, T_n\) 上 \(1 \rightsquigarrow n\) 的路径等于 \(P\)(最短路树并不是唯一的)。
枚举每一条边 \((u, v)\)。若 \(T_1(1 \rightsquigarrow u)\) 和 \(T_n(v \rightsquigarrow n)\) 均不包含 \(e\),即可用 \(w(T_1(1\rightsquigarrow u)) + w(u \rightarrow v) + w(T_n(v \rightsquigarrow n))\) 更新最短路,
设 \(l_u\) 表示最短路树 \(T_1\) 上 \(1 \rightsquigarrow u\) 与 \(P\) 第一条不相交的边,\(r_v\) 表示最短路树 \(T_n\) 上节点 \(v \rightsquigarrow n\) 与 \(P\) 第一条不重合的边。
\(l_u\) 与 \(r_v\) 时容易求的,首先,若 \((u, v) \in P\),那么 \(l_u = u\) 下一条边的编号,\(r_v = v\) 上一条边的编号。
而当 \((u, v) \notin P\) 时,\(l_v = \min(l_u, l_v), r_v = \max(r_u, r_v)\)(注意边是无向的)。
对于一条不在 \(P\) 上的边 \((u, v)\),若删去 \(l_u \rightsquigarrow r_v\) 中任一边,新的最短路就可能经过 \((u, v)\),否则,必然不经过 \((u, v)\)。
于是可以先预处理出删除 \(l_u \rightsquigarrow r_v\) 后可能的结果。用 multiset
来维护考虑删除 \(l_u \rightsquigarrow r_v\) 中任一边后新的最短路的最小值即可。
具体来说:定义两个数组 \(A, B\),\(A_i\) 用来记录 \(l_u = i\)(\(i\) 为 \(P\) 中边的编号) 的可能的最短路值,\(B_i\) 用来记录 \(r_v = i\) 时的最短路值,用 multiset
容器定义一个 \(Min\) 来维护考虑到 \(i\) 时可能的最短路。
当考虑到编号为 \(i\) 的边时,将 \(A_i\) 中数加入到 \(Min\) 中,考虑完后,将 \(B_i\) 中的数从 \(Min\) 中删去。这样就可以准确的维护删去 \(i\) 时的最短路。
例题
P2685 [TJOI2012] 桥
题意简述:给定一张自环和重边的无向正权图,求删去某条边,使最短路最大。
求该最短大小和删去边的方案。
模版题。
点击查看代码
/*
--------------------------------
| code by FRZ_29 |
| code time |
| 2024/08/31 |
| 08:31:10 |
| 星期六 |
--------------------------------
*/
#include <algorithm>
#include <iostream>
#include <climits>
#include <cstring>
#include <cstdio>
#include <vector>
#include <ctime>
#include <queue>
#include <set>
using namespace std;
void RD() {}
template<typename T, typename... U> void RD(T &x, U&... arg) {
x = 0; int f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
x *= f; RD(arg...);
}
const int N = 1e5 + 4, M = 2e5 + 5;
#define FI first
#define SE second
#define PB push_back
#define PII pair<int, int>
#define FILL(x, y) memset(x, y, sizeof(x))
#define PRINT(x) cout << #x << "=" << x << "\n"
#define LF(i, __l, __r) for (int i = __l; i <= __r; i++)
#define RF(i, __r, __l) for (int i = __r; i >= __l; i--)
struct Edge { int u, v, w; } ed[M];
int head[N], ver[M << 1], Next[M << 1], edge[M << 1], tot = 1;
int dis1[N], dis2[N], pre[N], len, ans;
int l[N], r[N], t[N], res;
multiset<int> Min;
vector<int> dp1[N], dp2[N];
bool vis[N], st[M << 1];
int n, m;
bool cmp1(int a, int b) {
return dis1[a] < dis1[b];
}
bool cmp2 (int a, int b) {
return dis2[a] < dis2[b];
}
void add(int u, int v, int w) {
ver[++tot] = v, edge[tot] = w;
Next[tot] = head[u], head[u] = tot;
}
void dijkstra(int s, int* dis) {
dis[s] = 0;
FILL(vis, false);
priority_queue<PII, vector<PII>, greater<PII>> que;
que.push({0, s});
while (que.size()) {
int u = que.top().SE;
que.pop();
if (vis[u]) continue;
vis[u] = true;
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (s == 1) pre[v] = i;
que.push({dis[v], v});
}
}
}
}
int main() {
// freopen("read.in", "r", stdin);
// freopen("out.out", "w", stdout);
// time_t st = clock();
RD(n, m);
FILL(dis1, 0x3f), FILL(dis2, 0x3f);
LF(i, 1, m) {
int u, v, w;
RD(u, v, w);
add(u, v, w), add(v, u, w);
ed[i] = {u, v, w};
}
dijkstra(1, dis1), dijkstra(n, dis2);
FILL(l, 0x3f);
l[1] = 1;
for (int i = n; i != 1; i = ver[pre[i] ^ 1]) len++, st[pre[i]] = st[pre[i] ^ 1] = true;
for (int i = n, cnt = len + 1; i != 1; i = ver[pre[i] ^ 1], cnt--) l[i] = cnt, r[i] = cnt - 1;
LF(i, 1, n) t[i] = i;
sort(t + 1, t + n + 1, cmp1);
LF(i, 1, n) {
int u = t[i];
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (st[i]) continue;
if (dis1[u] + w == dis1[v] && l[v] > l[u]) l[v] = l[u];
}
}
LF(i, 1, n) t[i] = i;
sort(t + 1, t + n + 1, cmp2);
LF(i, 1, n) {
int u = t[i];
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (st[i]) continue;
if (dis2[u] + w == dis2[v] && r[v] < r[u]) r[v] = r[u];
}
}
LF(i, 1, m) {
if (st[i << 1]) continue;
int u = ed[i].u, v = ed[i].v, w = ed[i].w;
if (l[u] <= r[v]) {
dp1[l[u]].PB(dis1[u] + w + dis2[v]);
dp2[r[v]].PB(dis1[u] + w + dis2[v]);
}
if (l[v] <= r[u]) {
dp1[l[v]].PB(dis1[v] + w + dis2[u]);
dp2[r[u]].PB(dis1[v] + w + dis2[u]);
}
}
LF(i, 1, len) {
for (int x : dp1[i]) Min.insert(x);
if (*Min.begin() > res) res = *Min.begin(), ans = 1;
else if (*Min.begin() == res) ans++;
for (int x : dp2[i]) Min.erase(Min.find(x));
}
if (res == dis1[n]) ans = m;
printf("%d %d", res, ans);
// printf("\n%dms", clock() - st);
return 0;
}
/* ps:FRZ弱爆了 */