说到求图的最小环,比较常见的是Floyed求,复杂度为O(n^3),比较费时。再优一点的,枚举所有边,每次删去一条边(u, v),然后从u开始跑Dijkstra(用堆或者c++中优先队列优化的)求u到v的距离再加上(u, v)的权值,枚完玩所有边就得出答案了,复杂度为O(m * n * logn)。然而这样的复杂度还是不令人满意。
看了好多博客,倒是发现了一个比较优的方法,我们先求所给图的最小生成树,可以保证最小环就是由最小生成树上的边再加上一条非生成树上的边构成的,依次枚举所有边,每次加上一条边后,求出由其构成的环的权值,最后求最小值就解决了图的最小环问题。
再说说具体怎么求每次的环的权值,我们把图变成了一颗树,假设我们枚举了一条边(u, v), 我们只需要在生成树上求出u到v的距离在加上(u, v)这一条边的权值就算是求出环的权值了,至于求u到v的距离,用最近公共祖先(lca)可以解决,求出u和v到其公共祖先的距离之和就是u到v的距离了。
我们来分析一下其复杂度,求LCA的话,O(n)的复杂度的求深度等信息,O(nlogn)的复杂度递推求grand数组,O(logn)的复杂度求LCA,综合起来差不多就是O(nlogn)级别的复杂度;求最小生成树的复杂度为O(mlogm + nlogn),其中nlogn的复杂度是排序的复杂度。所以说整个求最小环的算法复杂度为O(nlogn + mlogm)级别的,效率算是比较高了。
复杂度算的有些懵,虽然网上说克鲁斯卡尔的复杂的为mlogm,但是我不太明白。如果又发现错误的地方欢迎指正。。。
具体代码实现如下:(对应于HDU - 6005)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 8010;
int t, m, num;
int depth[maxn], fa[maxn], grand[maxn][20], dist[maxn], pre[maxn];
bool vis[maxn]; //用来标记编号为i的边是否在最小生成树上面
struct Edge {
int u, v, w;
bool operator < (const Edge x) const {
return w < x.w;
}
}edge[maxn];
map < pair < int, int >, int > ma; //仅仅本题用到,用来把坐标映射成节点编号
pair < int, int > a, b;
vector < int > gra[maxn], val[maxn]; //用来存最小生成树以及权值
inline void Init() {
num = 0;
ma.clear(); //每次清空map
memset(vis, false, sizeof(vis));
memset(fa, 0, sizeof(fa));
for(int i = 1; i <= 8000; ++ i) {
gra[i].clear();
val[i].clear();
pre[i] = i; //并查集初始化
}
}
int finds(int x) { //并查集
if(pre[x] == x)
return x;
return pre[x] = finds(pre[x]);
}
void Kruskal() { //克鲁斯卡尔求最小生成树
sort(edge + 1, edge + 1 + m);
for(int i = 1; i <= m; ++ i) {
int u = edge[i].u, v = edge[i].v, w = edge[i].w;
int a = finds(u), b = finds(v);
if(a != b) {
vis[i] = true;
pre[a] = b;
gra[u].push_back(v), gra[v].push_back(u);
val[u].push_back(w), val[v].push_back(w);
}
}
}
void dfs(int u, int father, int dep) { //用一遍dfs求一下所需要的深度以及其他信息
depth[u] = dep;
grand[u][0] = father;
fa[u] = father;
for(int i = 0; i < (int)gra[u].size(); ++ i) {
int v = gra[u][i], w = val[u][i];
if(v != father) {
dist[v] = dist[u] + w; //求节点v到根节点的距离
dfs(v, u, dep + 1);
}
}
}
void lca_init() { //递推初始化grand数组
for(int j = 1; j <= (int)log2(num) + 1; ++ j) {
for(int i = 1; i <= num; ++ i) {
grand[i][j] = grand[grand[i][j - 1]][j - 1];
}
}
}
int lca(int a, int b) { //倍增求lca
if(depth[a] < depth[b])
swap(a, b);
int u = a, v = b, def = depth[a] - depth[b];
for(int i = 0; i <= 15; ++ i) { //跳到同一深度
if(def & (1 << i)) {
a = grand[a][i];
}
}
if(a == b)
return dist[u] - dist[a];
for(int i = 15; i >= 0; -- i) {
if(grand[a][i] != grand[b][i]) {
a = grand[a][i];
b = grand[b][i];
}
}
return dist[u] + dist[v] - 2 * dist[grand[a][0]];
}
int main()
{
//freopen("in.txt", "r", stdin);
int Case = 0;
cin >> t;
while(t --)
{
cin >> m;
int x1, y1, x2, y2, w;
Init();
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &w);
a.first = x1, a.second = y1;
b.first = x2, b.second = y2;
if(!ma[a]) ma[a] = ++ num;
if(!ma[b]) ma[b] = ++ num;
edge[i].u = ma[a];
edge[i].v = ma[b];
edge[i].w = w;
}
Kruskal();
for(int i = 1; i <= num; ++ i)
{
if(!fa[i])
{
dist[i] = 0;
dfs(i, -1, 0);
}
}
lca_init();
int mins = 0x3f3f3f3f;
for(int i = 1; i <= m; ++ i)
{
if(vis[i]) continue; //如果此边在生成树中,则跳过
int u = edge[i].u, v = edge[i].v, w = edge[i].w;
mins = min(mins, lca(u, v) + w);
}
if(mins == 0x3f3f3f3f)
mins = 0;
printf("Case #%d: %d\n", ++Case, mins);
}
return 0;
}