图论学习笔记

最短路

割边最短路

约定

\(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弱爆了 */
posted @ 2024-08-31 10:59  FRZ_29  阅读(20)  评论(0编辑  收藏  举报