题目描述:
给定一张 N 个点 M 条边的无向图,求无向图的严格次小生成树。
设最小生成树的边权之和为 sum,严格次小生成树就是指边权之和大于 sum 的生成树中最小的一个。
输入格式
第一行包含两个整数 N 和 M。
接下来 M 行,每行包含三个整数 x,y,z,表示点 x 和点 y 之前存在一条边,边的权值为 z。
输出格式
包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)
数据范围
N≤105,M≤3×105
输入样例:
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
输出样例:
11
思路:
本题是朴素版次小生成树的优化版本,相比于原版本,本版本的时空利用率更高,可以处理更多的点。
与朴素版类似,首先先用克鲁斯卡尔算法建树,并标记树边。
接下来用bfs来初始化depth,d1,d2。
其中
d
e
p
t
h
[
i
]
表
示
i
点
相
对
于
r
o
o
t
的
深
度
,
d
1
[
i
]
[
k
]
表
示
i
点
向
上
走
2
k
次
的
过
程
中
最
大
边
,
d
2
[
i
]
[
k
]
则
表
示
i
点
向
上
走
2
k
次
的
过
程
中
的
次
大
边
depth[i]表示i点相对于root的深度,d1[i][k]表示i点向上走2^k次的过程中最大边,d2[i][k]则表示i点向上走2^k次的过程中的次大边
depth[i]表示i点相对于root的深度,d1[i][k]表示i点向上走2k次的过程中最大边,d2[i][k]则表示i点向上走2k次的过程中的次大边
不同之处在于,针对d[4]数组。相当于在
a
与
a
+
2
k
点
之
间
再
选
一
个
a
+
2
k
−
1
点
a
n
c
,
a
到
a
+
2
k
点
中
的
最
大
值
为
d
1
[
j
]
[
k
−
1
]
,
d
2
[
j
]
[
k
−
1
]
,
d
1
[
a
n
c
]
[
k
−
1
]
,
d
2
[
a
n
c
]
[
k
−
1
]
中
的
一
个
,
次
大
值
同
理
a与a+2^k点之间再选一个a+2^{k-1}点anc,a到a+2^k点中的最大值为{ d1[j][k - 1],d2[j][k - 1],d1[anc][k - 1],d2[anc][k - 1] }中的一个,次大值同理
a与a+2k点之间再选一个a+2k−1点anc,a到a+2k点中的最大值为d1[j][k−1],d2[j][k−1],d1[anc][k−1],d2[anc][k−1]中的一个,次大值同理
在lca中,给出两个点a,b 以及两点间的非树边w。需要找到两点间一条最短的非树边dist,来替换掉原来的树边。
其中dist需要满足在所有a,b之间的树点中,w-dist最小,这样才能保证是最小生成树。
为了达成这个条件,需要倍增得到区间内所有的dist1,dist2,全都加进d数组中。在d数组中,选择出dist1,dist2 。若非树边与dist1相等则用dist2,反之则用dist1。但是若w比dist和dist2都小,则不存在该种情况,返回INF。
代码:
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e5 + 10, M = 3e5 + 10, INF = 0x3f3f3f3f;
struct Edge
{
int u, v, w;
bool used;
bool operator<(const Edge & t) const
{
return w < t.w;
}
}edge[M];
int n, m;
int p[N];
int e[M], ne[M], w[M], h[M], idx;
int fa[N][20];
int depth[N];
int d1[N][20], d2[N][20];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int find(int x)
{
if (x == p[x])
{
return x;
}
else
{
return p[x] = find(p[x]);
}
}
long long kruskal()
{
long long sum = 0;
for (int i = 1; i <= n; i++)
{
p[i] = i;
}
for (int i = 0; i < m; i++)
{
int u = edge[i].u;
int v = edge[i].v;
int w = edge[i].w;
int a = find(u);
int b = find(v);
if (a != b)
{
p[a] = b;
sum += w;
edge[i].used = true;
add(u, v, w);
add(v, u, w);
}
}
return sum;
}
void bfs(int root)
{
memset(depth, 0x3f, sizeof depth);
queue<int>q;
q.push(root);
depth[root] = 1;
depth[0] = 0;
while (!q.empty())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
fa[j][0] = t;
d1[j][0] = w[i];
d2[j][0] = -INF;
q.push(j);
for (int k = 1; k < 20; k++)
{
int anc = fa[j][k - 1];
fa[j][k] = fa[anc][k - 1];
int d[4] = { d1[j][k - 1],d2[j][k - 1],d1[anc][k - 1],d2[anc][k - 1] };
d1[j][k] = -INF;
d2[j][k] = -INF;
for (int u = 0; u < 4; u++)
{
int dd = d[u];
if (dd > d1[j][k])
{
d2[j][k] = d1[j][k];
d1[j][k] = dd;
}
else if (dd!=d1[j][k] && dd>d2[j][k])
{
d2[j][k] = dd;
}
}
}
}
}
}
}
long long lca(int u, int v, int w)
{
static int d[N * 2];
int cnt = 0;
if (depth[u] < depth[v])
{
swap(u, v);
}
for (int k = 19; k >= 0; k--)
{
if (depth[fa[u][k]] >= depth[v])
{
d[cnt++] = d1[u][k];
d[cnt++] = d2[u][k];
u = fa[u][k];
}
}
if (u != v)
{
for (int k = 19; k >= 0; k--)
{
if (fa[u][k] != fa[v][k])
{
d[cnt++] = d1[u][k];
d[cnt++] = d2[u][k];
d[cnt++] = d1[v][k];
d[cnt++] = d2[v][k];
u = fa[u][k];
v = fa[v][k];
}
}
d[cnt++] = d1[u][0];
d[cnt++] = d1[v][0];
}
int dist1 = -INF;
int dist2 = -INF;
for (int i = 0; i < cnt; i++)
{
if (d[i] > dist1)
{
dist2 = dist1;
dist1 = d[i];
}
else if (d[i]!=dist1 && d[i]>dist2)
{
dist2 = d[i];
}
}
if (w > dist1) return w - dist1;
if (w > dist2) return w - dist2;
return INF;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> edge[i].u >> edge[i].v >> edge[i].w;
}
sort(edge, edge + m);
long long sum = kruskal();
int root = 1;
bfs(root);
long long res = 1e18;
for (int i = 0; i < m; i++)
{
if (!edge[i].used)
{
res = min(res, sum+lca(edge[i].u, edge[i].v, edge[i].w));
}
}
cout << res;
}