Loading

[gym 100917F] Find the Length

算法

转化题意:

给出一个无向正权无自环图 要求对于每个点 经过它的最短"简单环"的长度

有一种错误的思路, 对于每次询问 我们以该点 \(s\) 作为起点

先处理出到其余每点的最短路, 从一条边走回来即可

这个思路容易找到反例, 具体的, 我们显然可以发现如果 \(s\) 分别到 \(u\)\(v\) 的最短路有重合, 那么环是不存在的, 考虑解决这个问题

这个时候 \(\rm{TJ}\) 给了一个神秘做法

首先, 建立 最短路径树, 然后我们可以发现一种很好的性质

例如, 假设对于 \(1\) 的最短路径树

pA5myOP.png

显然的, 如果不在最短路径树中, 一定不优

我们可以发现, 如果能够构成环, 一定要引入一条非树边

成环无非这几种情况
pA5mxp9.png

  • \(\color{red}\text{红边}\)
    链接树上一点与根节点, 特别的, 这一点不能与根节点有直接连接

  • \(\color{yellow}\text{黄边}\)
    链接树上两点

枚举符合条件的边, 更新答案

代码

流程

  1. 枚举起始点 \(s\)
  2. 构造最短路生成树
  3. 构造 \((s, u, w) , u \notin \rm{Son} (s)\) , 用 \(w + dis_u\) 更新答案
  4. 构造 \((u, v, w) , u, v \neq s\) , 用 \(dis_u + dis_v + w\) 更新答案

复杂度 \(\mathcal{O}\left[n \times (n + m) \log n\right]\) , 大概是 \(\mathcal{O} (n ^ 3 + n ^ 2 \log n)\)

实现

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;
typedef long long LL;
const int MAXN = 305;
const int INF = 4e8;
int n;
int ans[MAXN];

int Map[MAXN][MAXN];
int pre[MAXN];
int findx(int x) {
    return (pre[x] == x) ? x : pre[x] = findx(pre[x]);
}

class Sol_Class
{
private:
    int dis[MAXN];
    bool vis[MAXN];
    void spfa(int s)
    {
        for (int i = 1; i <= n; ++i) {
            dis[i] = INF;
            pre[i] = i;
        }
        deque<int> q;
        q.push_back(s);
        dis[s] = 0;
        int cnt = 1,sum = 0;
        while (!q.empty())
        {
            int u = q.front();
            q.pop_front();
            if (dis[u] * cnt > sum) {
                q.push_back(u);
                continue;
            }

            vis[u] = 0;
            cnt--, sum -= dis[u];
            for (int v = 1; v <= n; ++v) {
                if (dis[u] + Map[u][v] < dis[v]) {
                    dis[v] = dis[u] + Map[u][v];
                    if (u != s) pre[v] = u;
                    if (!vis[v]) {
                        vis[v] = 1;
                        cnt++, sum += dis[v];
                        if (q.empty() || dis[v] > dis[q.front()]) q.push_back(v);
                        else q.push_front(v);
                    }
                }
            }
        }
    }

public:
    void solve()
    {
        for (int s = 1; s <= n; ++s)
        {
            spfa(s);
            /*红边*/
            for (int i = 1; i <= n; ++i) 
                if (pre[i] != i) 
                    ans[s] = min(ans[s], Map[s][i] + dis[i]);
            /*黄边*/
            for (int i = 1; i <= n; ++i)
                for (int j = 1; j <= n; ++j) {
                    if (i != s && j != s && findx(i) != findx(j))
                        ans[s] = min(ans[s], dis[i] + dis[j] + Map[i][j]);
                }
        }
    }
} Sol;

int main()
{
    int Sub;
    scanf("%d %d", &Sub, &n);
    for (int i = 1; i <= n; ++i)
        for (int j = i + 1; j <= n; ++j)
        {
            scanf("%d", &Map[i][j]);
            if (Map[i][j] == -1) Map[i][j] = INF;
            Map[j][i] = Map[i][j];
        }

    for (int i = 1; i <= n; ++i) ans[i] = INF;

    Sol.solve();

    for (int i = 1; i <= n; ++i) printf((ans[i] == INF) ? "-1 " : "%d ", ans[i]);
    return 0;
}

总结

\(\rm{trick}\) : 每个小部分单独计算贡献

全新方法 : 最短路径树

posted @ 2024-11-28 20:53  Yorg  阅读(27)  评论(0)    收藏  举报