[gym 100917F] Find the Length
算法
转化题意:
给出一个无向正权无自环图 要求对于每个点 经过它的最短"简单环"的长度
有一种错误的思路, 对于每次询问 我们以该点 \(s\) 作为起点
先处理出到其余每点的最短路, 从一条边走回来即可
这个思路容易找到反例, 具体的, 我们显然可以发现如果 \(s\) 分别到 \(u\) 和 \(v\) 的最短路有重合, 那么环是不存在的, 考虑解决这个问题
这个时候 \(\rm{TJ}\) 给了一个神秘做法
首先, 建立 最短路径树, 然后我们可以发现一种很好的性质
例如, 假设对于 \(1\) 的最短路径树
显然的, 如果不在最短路径树中, 一定不优
我们可以发现, 如果能够构成环, 一定要引入一条非树边
-
\(\color{red}\text{红边}\)
链接树上一点与根节点, 特别的, 这一点不能与根节点有直接连接 -
\(\color{yellow}\text{黄边}\)
链接树上两点
枚举符合条件的边, 更新答案
代码
流程
- 枚举起始点 \(s\)
- 构造最短路生成树
- 构造 \((s, u, w) , u \notin \rm{Son} (s)\) , 用 \(w + dis_u\) 更新答案
- 构造 \((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}\) : 每个小部分单独计算贡献
全新方法 : 最短路径树



浙公网安备 33010602011771号