最小直径生成树
问题描述:求无向图中直径最小的生成树
图的绝对中心
定义:图上的某个在节点或边上的点,使得到该点最远的点的距离最小。根据 图的绝对中心 的定义可以知道,到绝对中心距离最远的结点至少有两个,且这两个最远点经过中心点的最短路是最小直径生成树的直径。
求解方法:设 d [ i ] [ j ] d[i][j] d[i][j] 表示图中节点 i , j i,j i,j 之间的最短路。 r k [ i ] [ j ] rk[i][j] rk[i][j] 表示到点 i i i 第 j j j 远的点。
如图, d [ c ] [ i ] = min ( d [ u ] [ i ] + x , d [ v ] [ i ] + ( w − x ) ) d[c][i]=\min(d[u][i]+x,d[v][i]+(w-x)) d[c][i]=min(d[u][i]+x,d[v][i]+(w−x))。
d [ c ] [ i ] d[c][i] d[c][i] 关于 x x x 的函数图像如下图所示,是由两个斜率绝对值相等的线段构成。
对该边上的点 c c c 到图上所有点的最短距离关于 c c c 在边上的位置,即 f ( x ) = max i ∈ [ 1 , n ] d [ c ] [ i ] f(x)=\max\limits_{i\in[1,n]}d[c][i] f(x)=i∈[1,n]maxd[c][i] 的图像作图,显然最低点对应的 c c c 点的位置是图的绝对中心的候选点之一。
现在问题转化为如何求函数的最小值。
设 a n s ans ans 为最小直径生成树的直径。
-
首先对于绝对中心在节点上的情况,更新 a n s = min i ∈ [ 1 , n ] ( a n s , d [ i ] [ r k [ n ] ] ⋅ 2 ) ans=\min\limits_{i\in[1,n]}(ans,d[i][rk[n]]\cdot 2) ans=i∈[1,n]min(ans,d[i][rk[n]]⋅2),即到图上到该点最短距离最大的点的距离的两倍。
-
对于绝对中心在边上的情况,做出 f ( x ) f(x) f(x) 图像如下图,发现当且仅当存在距离该边的某一端点 u u u 距离相大小邻的两个点 i 1 , i 2 i_1,i_2 i1,i2 使得 d [ u ] [ i 1 ] < d [ u ] [ i 2 ] d[u][i_1]<d[u][i_2] d[u][i1]<d[u][i2] 且 d [ v ] [ i 1 ] > d [ v ] [ i 2 ] d[v][i_1]>d[v][i_2] d[v][i1]>d[v][i2] 时针对两点 i 1 , i 2 i_1,i_2 i1,i2 的最短距离图像的交点的函数值为函数 f ( x ) f(x) f(x) 的极小值。枚举这样的极小值就可以得到绝对中心在边上的情况的候选点。
综合两种情况就可以得到图的绝对中心即最小直径生成树的直径 a n s ans ans 了。
时间复杂度 O ( n 3 log n ) O(n^3\log n) O(n3logn) 。
模板:
const int N = 1005;
struct MDST {
int d[N][N], n, m, tot;
int rk[N][N];
struct Edge {
int x, y, z;
} e[(N * N) >> 1];
inline void floyd() {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
inline void add(int x, int y, int z) {
if (d[x][y] <= z)return;
e[++tot] = {x, y, z};
d[x][y] = d[y][x] = z;
}
inline void solve() {
memset(d, 0x3f, sizeof(d));
n = qr(), m = qr();
for (int i = 1; i <= n; i++)d[i][i] = 0;
while (m--) {
int x = qr(), y = qr(), z = qr();
add(x, y, z);
}
floyd();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)rk[i][j] = j;
sort(rk[i] + 1, rk[i] + n + 1, [&](int x, int y) { return d[i][x] < d[i][y]; });
}
int ans = INF;
for (int i = 1; i <= n; i++)ans = min(ans, d[i][rk[i][n]] * 2);
for (int i = 1; i <= tot; i++)
for (int j = n - 1, k = n; j >= 1; j--)
if (d[e[i].y][rk[e[i].x][j]] > d[e[i].y][rk[e[i].x][k]])
ans = min(ans, d[e[i].x][rk[e[i].x][j]] + d[e[i].y][rk[e[i].x][k]] + e[i].z), k = j;
printf("%d\n", ans);
}
} M;
最小直径生成树
以绝对中心为起点,生成一棵最短路径树。
模板:
完全图Dijkstra换成 O ( n 2 ) O(n^2) O(n2) 版。
const int N = 205;
struct MDST {
int g[N][N], n, m, tot;
int rk[N][N], d[N][N];
int fa[N];
struct Edge {
int x, y, z;
} e[(N * N) >> 1];
struct {
int x, y;
double d;
} key;
inline void floyd() {
memcpy(d, g, sizeof(d));
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
inline void add(int x, int y, int z) {
if (g[x][y] <= z)return;
e[++tot] = {x, y, z};
g[x][y] = g[y][x] = z;
}
double dis[N];
bool v[N];
struct node {
int x;
double d;
inline bool operator<(node a) const {
return d > a.d;
}
};
priority_queue<node> q;
inline void dijkstra() {
for (int i = 1; i <= n; i++)dis[i] = 1e18;
memset(v, false, sizeof(bool) * (n + 1));
if (key.y) {
q.push({key.x, key.d});
dis[key.x] = key.d;
q.push({key.y, g[key.x][key.y] - key.d});
dis[key.y] = g[key.x][key.y] - key.d;
} else q.push({key.x, 0}), dis[key.x] = 0;
while (!q.empty()) {
node t = q.top();
q.pop();
if (v[t.x])continue;
v[t.x] = true;
for (int y = 1; y <= n; y++)
if (dis[y] > t.d + g[t.x][y]) {
fa[y] = t.x;
dis[y] = t.d + g[t.x][y];
q.push({y, dis[y]});
}
}
for (int i = 1; i <= n; i++)
if (fa[i]) printf("%d %d\n", i, fa[i]);
if (key.y) printf("%d %d\n", key.x, key.y);
}
/*void dijkstra() {
for (int i = 1; i <= n; i++)dis[i] = 1e18;
memset(v, false, sizeof(bool) * (n + 1));
dis[key.x] = key.d;
if (key.y)dis[key.y] = g[key.x][key.y] - key.d;
for (int i = 1; i < n; i++) {
int x = 0;
for (int j = 1; j <= n; j++)
if (!v[j] && (x == 0 || dis[j] < dis[x])) x = j;
v[x] = true;
for (int y = 1; y <= n; y++)
if (dis[y] > dis[x] + g[x][y])fa[y] = x, dis[y] = dis[x] + g[x][y];
}
for (int i = 1; i <= n; i++)
printf("%d %d\n", i, fa[i]);
if (key.y) printf("%d %d\n", key.x, key.y);
}*/
inline void solve() {
memset(g, 0x3f, sizeof(g));
n = qr(), m = qr();
for (int i = 1; i <= n; i++)g[i][i] = 0;
while (m--) {
int x = qr(), y = qr(), z = qr();
add(x, y, z);
}
floyd();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)rk[i][j] = j;
sort(rk[i] + 1, rk[i] + n + 1, [&](int x, int y) { return d[i][x] < d[i][y]; });
}
int ans = INF;
for (int i = 1; i <= n; i++)
if (ans > d[i][rk[i][n]] * 2) {
ans = d[i][rk[i][n]] * 2;
key = {i, 0, 0};
}
for (int i = 1; i <= tot; i++)
for (int j = n - 1, k = n; j >= 1; j--)
if (d[e[i].y][rk[e[i].x][j]] > d[e[i].y][rk[e[i].x][k]]) {
int len = d[e[i].x][rk[e[i].x][j]] + d[e[i].y][rk[e[i].x][k]] + e[i].z;
k = j;
if (ans <= len)continue;
key = {e[i].x, e[i].y, (1.0 * len) / 2 - d[e[i].x][rk[e[i].x][j]]};
ans = len;
}
printf("%d\n", ans);
dijkstra();
}
} M;