day10 最短路

最短路


最短路算法

  • Floyd 算法:多源最短路
  • Dijkstra 算法:单源最短路
  • SPFA 算法:判负环
  • 特殊图最短路:
    • 带负权最短路
    • 次短路
    • 边权只有 0/1 的最短路

次短路

  • 最短路树:求出最短路后,每个源点 i 有且仅有一个转移过来的父亲,形成了一个树形结构

  • 这张图上还有一些非树边

  • 次短路性质:本质上是经过不超过一条非树边的路

  • 因为走树边永远比非树边要更优

  • 枚举一条不在最短路上的非树边 x\to y ,强制经过该边,其他走的边都在树边上

    for ((x,y)不在最短路上) ans = min(ans,dis[x] + w[x,y] + dis[y]);
    
  • 可以拓展到 k 短路 (复杂度 O(k\log m)

差分约束系统

  • 如果是 x_i -x_j\ge c_k ? 反过来建边
  • 把所有关系全部转化为 x_i\le x_j+c_k
  • x_i 能取的区间就是所有限制上最紧的
  • 如果是张 DAG 就可以直接 dp 出来
  • 如果不是?
  • 建一个虚拟节点 s ,连几条向起点、边权 0 的边
  • 如果最短路可解,则差分约束系统就可解
  • 如果要求某个元素最大,就求最短路
  • 如果要求某个元素最小,就求最长路
  • 差分约束详解

有向图最小环

  • Floyd 求解

例题

P5304 [GXOI/GZOI2019] 旅行者

给定一个 n 个点 m 条边的有向带权图,其中有 k 个特殊点,求这 k 个特殊点之间两两最短路的最小值

  • 把这 k 个点按照二进制分组
  • 当前位为 0 的点扔进起点集合,当前位为 1 的点扔进终点集合
  • 时间复杂度 O(dis\log m)

例题3

给定一个无向带权连通图,求节点两两之间最短路径从小到大排序之后第 k 长的路径的长度

  • 肯定不会用到经过比第 k 小的边还小的路径
  • 所以可以删掉这些边
  • 这样删下来只会剩下至多 800 个点

例题4

给定一张 N 个点 N 条边的有向图,每个点出度为 1

求最少增加多少条有向边使得:从 1 到每个点的最短路径不大于 K

1\le N\le 10^6

  • 先把所有叶子节点和 1 进行连边
  • 从叶子节点往上走并更新答案
  • 这样就只会剩下一个环
  • 先把已经满足要求的点染色
  • 枚举起点从 1k 时答案会怎么样

Shortest Path

  • 设状态 f[i][j] 表示当前为 j ,前一个为 i 时的答案
  • 转移时如果 ia_xjb_x ,那就不能向 f[j][c_x] 转移
  • 此时如果 ij 不满足上面的要求,可以直接记录状态为 f[0][j] ,减少状态数

P1363 幻象迷宫

  • 所以不能拓展迷宫而对坐标取模就好了.

  • 如果走到过某个点现在又走到了这个点,那显然是可以走无限远的。

  • 现在出现了一些~~(堆)~~问题:如何判断是否走到过这个点呢?

  • 有一个比较巧妙的方法:

  • 记录取模的横纵坐标 x,y 时,同时记录没有取模的坐标 lx, ly

  • 当第一次走这个迷宫的时候, x, ylx, ly 肯定是分别相等的

  • 所以只要走到的一个点的 x, ylx, ly 不相等

    x≠lx ∣∣ y≠lyx\\=lx ∣∣ y\\=ly

  • 那这个点一定是被走了第二遍

故乡的梦

给出一个带权无向图,起点 S 和终点 TQ 个询问。每个询问询问删除某条边后 ST 的最短路。 1\le N,M,Q\le2\times10^5

P4246 [SHOI2008] 堵塞的交通

  • 线段树维护区间连通信息(4个点&上下两个点)
  • 剩下就是代码问题(悲

课后题


[JSOI2007]重要的城市

  • 跑 Floyd ,如果当前中转点 k 能更新 f[i][j] 的答案,那 k 就是 (i,j) 的重要城市

  • 如果 (i,j) 存在不止一条最短路,那 (i,j) 就没有重要城市

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int maxn = 205;
    bool ans[maxn];
    int n,m; ll mp[maxn][maxn]; int imp[maxn][maxn];
    int main () {
        scanf("%d%d",&n,&m); 
        for (int i = 1;i <= n;i ++)
            for (int j = 1;j <= n;j ++)
                if (i != j) mp[i][j] = 1e18;
        for (int i = 1,x,y,v;i <= m;i ++) {
            scanf("%d%d%d",&x,&y,&v); mp[y][x] = mp[x][y] = (ll)(v); }
        for (int k = 1;k <= n;k ++)
            for (int i = 1;i <= n;i ++)
                for (int j = 1;j <= n;j ++)
                if (i != j && j != k && i != k) {
                    if (mp[i][j] > mp[i][k] + mp[k][j]) mp[i][j] = mp[i][k] + mp[k][j],imp[i][j] = k;
                    else if (mp[i][j] == mp[i][k] + mp[k][j]) imp[i][j] = -1;
                }
        for (int i = 1;i <= n;i ++)
            for (int j = 1;j <= n;j ++)
                if (imp[i][j] != -1)
                    ans[imp[i][j]] = true;
        bool ok = true;
        for (int i = 1;i <= n;i ++)
            if (ans[i]) { printf("%d ",i); ok = false; }
        if (ok) printf("No important cities.");
        
        return 0;
    }
    

CQOI新年好

  • 对每个亲戚都跑一遍最短路,暴力枚举亲戚的先后访问顺序

    #include<bits/stdc++.h>
    #define type pair<int,int> 
    #define int long long
    #define mk make_pair
    using namespace std;
    const int maxn = 200005;
    int n,m,s[10],dis[10][maxn],ans = 2e9; vector<type> mp[maxn]; bool vis[maxn];
    void addEdge(int u,int v,int w) { mp[u].push_back(mk(v,w)); }
    void Dijkstra(int id) {
        priority_queue<type,vector<type>,greater<type> > q;
        dis[id][s[id]] = 0; q.push(mk(0,s[id])); 
        while (!q.empty()) {
            type top = q.top(); q.pop();
            int u = top.second; vis[u] = true;
            for (auto v : mp[u]) 
                if (dis[id][v.first] > dis[id][u] + v.second) {
                    dis[id][v.first] = dis[id][u] + v.second;
                    if (!vis[v.first]) q.push(mk(dis[id][v.first],v.first));
                }
        }
        memset(vis,0,sizeof(vis));
    }
    void dfs(int pos,int lst, int sum) {
        if (pos > 6) { ans = min(ans,sum); return ; }
        if (sum > ans) return ;
        for (int i = 2;i <= 6;i ++)
            if (!vis[i] && dis[lst][s[i]] != 0x3f3f3f3f) {
                vis[i] = true;
                dfs(pos + 1,i,sum + dis[lst][s[i]]);
                vis[i] = false;
            }
    }
    signed main() {
        scanf("%lld%lld",&n,&m); s[1] = 1; memset(dis,0x3f,sizeof(dis));
        for (int i = 2;i <= 6;i ++) scanf("%lld",&s[i]);
        for (int i = 1,u,v,w;i <= m;i ++) {
            scanf("%lld%lld%lld",&u,&v,&w);
            addEdge(u,v,w); addEdge(v,u,w);
        }
        for (int i = 1;i <= 6;i ++) Dijkstra(i);
        dfs(2,1,0);
        printf("%lld",ans);
    }
    /*
    6 6
    2 3 4 5 6
    1 2 8
    2 3 3
    3 4 4
    4 5 5
    5 6 2
    */
    

JLOI2011飞行路线

  • dp[i][j] 表示当前转移到 i 点,使用 j 条免费路线

  • 初始化 dp[i][0] 为起点到 i 的最短路

  • 用求最短路的方法进行转移,假设当前点为 u ,要从 v 转移状态

  • dp[u][j] = \min(dp[v][j - 1],dp[v][j] + w[u\to v])

  • 枚举 j1\to k 转移状态

  • 答案从 dp[t][0\to k]\min

    /*
    dp[i][k]
    -> 从起点飞到i城市,使用k条免费路线的最小代价
    初始化 -> d[i][0] = 从起点到这里的最短路
    转移 -> d[v][k] = min(d[u][k - 1],d[u][k] + w)
    答案在d[终点][0 ~ k] 中取min
    徒手切爆蓝题了耶 qwq
    */
    #include<bits/stdc++.h>
    #define mk make_pair
    #define type pair<int,int>
    using namespace std;
    const int maxn = 10005,maxk = 15; bool vis[maxn];
    int n,m,k,s,t,dp[maxn][maxk],dis[maxn],ans = 2e9; vector<type> mp[maxn];
    void addEdge(int u,int v,int w) { mp[u].push_back(mk(v,w)); }
    void Init() {
        priority_queue<type,vector<type>,greater<type> > q;
        memset(dis,0x3f,sizeof(dis)); dis[s] = 0; q.push(mk(0,s));
        while (!q.empty()) {
            type top = q.top(); q.pop();
            int u = top.second; vis[u] = true;
            for (auto v : mp[u]) 
                if (dis[v.first] > dis[u] + v.second) {
                    dis[v.first] = dis[u] + v.second;
                    if (!vis[v.first]) q.push(mk(dis[v.first],v.first));
                }
        }
        memset(dp,0x3f,sizeof(dp));
        for (int i = 1;i <= n;i ++) dp[i][0] = dis[i];
    }
    void Solve() {
        priority_queue<type,vector<type>,greater<type> > q;
        memset(vis,false,sizeof(vis)); q.push(mk(dp[s][0],s));
        while (!q.empty()) {
            type top = q.top(); q.pop();
            int u = top.second; vis[u] = true;
            for (auto v : mp[u]) 
                for (int i = 1;i <= k;i ++)
                    if (dp[v.first][i] > min(dp[u][i] + v.second,dp[u][i - 1])) {
                        dp[v.first][i] = min(dp[u][i] + v.second,dp[u][i - 1]);
                        if (!vis[v.first]) q.push(mk(dp[v.first][i],v.first));
                    }
    
        }
    }
    int main() {
        scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
        s ++, t ++; // 下标统一从1开始
        for (int i = 1,u,v,w;i <= m;i ++) {
            scanf("%d%d%d",&u,&v,&w);
            addEdge(u + 1,v + 1,w); addEdge(v + 1,u + 1,w);
        }
        Init(); Solve(); 
        for (int i = 0; i <= k;i ++) ans = min(ans,dp[t][i]);
        printf("%d",ans);
        return 0;
    }
    

护送

  • Floyd

  • 设边的最短路转移矩阵为 f ,加上点权后的最短路转移矩阵为 g

  • 按照点权从小到大进行转移

  • g[i][j] = \min(f[i][j] + v[k]) 其中 v 表示点权, k 为中转点

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn = 605;
    struct hyf { int id; ll value; } c[maxn];
    ll nc[maxn], f[maxn][maxn], g[maxn][maxn]; int n, m, q;
    bool cmp(const hyf &x, const hyf &y) { return x.value < y.value; }
    int main() {
        scanf("%d%d", &n, &m);
        memset(f, 127 / 3, sizeof(f));
        memset(g, 127 / 3, sizeof(g));
        for (int i = 1; i <= n; i ++) {
            scanf("%lld", &c[i].value);
            nc[i] = c[i].value, c[i].id = i;
        }
        for (int i = 1, u, v; i <= m; i ++) {
            ll w; scanf("%d%d%lld", &u, &v, &w);
            f[u][v] = f[v][u] = min(f[u][v], w);
        }
        sort(c + 1, c + n + 1, cmp);
        for (int t = 1,k; t <= n; t++) {
            k = c[t].id;
            for (int i = 1; i <= n; i ++) 
                for (int j = 1; j <= n; j ++) 
                    if (f[i][j] >= (f[i][k] + f[k][j]))
                        f[i][j] = f[i][k] + f[k][j];
            for (int i = 1; i <= t; i ++) 
                for (int j = 1; j <= t; j ++) 
                    g[c[i].id][c[j].id] = min(g[c[i].id][c[j].id], f[c[i].id][c[j].id] + c[t].value);
        }
        scanf("%d", &q);
        for (int i = 1, s, t; i <= q; i ++) {
            scanf("%d%d", &s, &t);
            printf("%lld\n", g[s][t]);
        }
        return 0;
    }
    

种树

  • 差分约束 + 前缀和

  • 可以建立出三个约束:(设树棵树的前缀和数组为 sum )

    1. sum[r]-sum[l-1]\ge c
    2. sum[i]-sum[i-1]\ge 0
    3. sum[i-1]-sum[i]\ge -k
  • 按照上述约束建边,SPFA 跑最长路即可

    #include<bits/stdc++.h>
    #define ll long long
    #define mk make_pair
    using namespace std;
    const int maxn = 500005;
    int sum[maxn],n,m,dis[maxn]; vector<pair<int,int> > mp[maxn]; bool vis[maxn];
    void addEdge(int u,int v,int w) { mp[u].push_back(mk(v,w)); }
    void Solve() {
        memset(dis,-0x3f,sizeof(dis));
        queue<int> q; dis[0] = 0; q.push(0);
        while (!q.empty()) {
            int u = q.front(); q.pop(); vis[u] = false;
            for (auto v : mp[u]) 
                if (dis[v.first] < dis[u] + v.second) {
                    dis[v.first] = dis[u] + v.second;
                    if (!vis[v.first]) { vis[v.first] = true; q.push(v.first); }
                }
        }
    }
    int main() {
        scanf("%d%d",&n,&m); addEdge(0,1,0); // 不要忘了这里还有一个约束
        for (int i = 1,x;i <= n;i ++) {
            scanf("%d",&x); sum[i] = sum[i - 1] + x;
            addEdge(i,i + 1,0); addEdge(i,i - 1,-x);
        }
        for (int i = 1,l,r,w;i <= m;i ++) {
            scanf("%d%d%d",&l,&r,&w);
            addEdge(l - 1,r,w);
        }
        Solve();
        printf("%d",dis[n]);
        return 0;
    }
    
2 个赞

膜拜大佬

2 个赞

我是蒟蒻

2 个赞

膜拜大佬

1 个赞

膜膜膜膜膜膜膜膜膜膜膜膜

2 个赞

wssb

3 个赞

泰剧了

2 个赞