题意:给你一个无向图(邻接矩阵),求每个点所在的最小环的长度,不存在输出-1。
思路:不是很难想到,包含点s的最小环,应该是s到其余点的最短路的边加上一条不是最短路的边,因为单纯靠最短路并不会形成环。这样的话我们先构造出以s为根,以s到其余点的最短路为边的生成树。然后我们去枚举这样的两个点。无非就两种情况:
1.额外边的一个端点包含点s的情况。
2.额外边的两个端点都不包含s的情况。
红色代表第一种情况,黄色代表第二种。可以看到第二种情况不能在同一棵子树中。用并查集维护一下就好了。
那么步骤就是:
1.枚举每个起始点s。
2.求出s到其余点的最短路,构造生成树。
3.枚举s到其余点j的各条边,此时环的长度 = dis[j]+tu[i][j]。
4.枚举s任意两棵子树上的两个点j,k的边,此时环的长度 = dis[j] + dis[k] + tu[j][k]。
#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 tu[MAXN][MAXN];
int pre[MAXN];
int findx(int x)
{
return pre[x] == x?x:pre[x] = findx(pre[x]);
}
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] + tu[u][v] < dis[v])
{
dis[v] = dis[u] + tu[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);
}
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
{
scanf("%d",&tu[i][j]);
if(tu[i][j] == -1)tu[i][j] = inf;
}
for(int i = 1; i <= n; ++i)ans[i] = inf;
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],tu[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] + tu[i][j]);
}
}
for(int i = 1; i <= n; ++i)printf(ans[i]==inf?"-1\n":"%d\n",ans[i]);
return 0;
}