356. 次小生成树
题目链接
356. 次小生成树
给定一张 \(N\) 个点 \(M\) 条边的无向图,求无向图的严格次小生成树。
设最小生成树的边权之和为 \(sum\),严格次小生成树就是指边权之和大于 \(sum\) 的生成树中最小的一个。
输入格式
第一行包含两个整数 \(N\) 和 \(M\)。
接下来 \(M\) 行,每行包含三个整数 \(x,y,z\),表示点 \(x\) 和点 \(y\) 之前存在一条边,边的权值为 \(z\)。
输出格式
包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)
数据范围
\(N≤10^5,M≤3×10^5\)
输入样例:
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
输出样例:
11
解题思路
倍增,最小生成树
先求出最小生成树,最小生成树中的边称为“树边”,对于每一条“非树边”,计算加上这条边后的最少代价,即加上这条边后会形成一个环,当权值最大的边的权值与当当前“非树边”的权值不相等时,需要在环去掉这条权值最大的边,否则去掉权值次大的边。所以关键在于寻找最小生成树中两点之间权值最大和次大的边,可类比LCA:
-
状态表示:\(dp[i][j][k]\) 表示 \(j\) 向上走 \(2^k\) 步的最大值(\(i=0\))/次大值(\(i=1\))
-
状态计算:
-
\(dp[0][y][i-1]==dp[0][f[y][i-1]][i-1]\),其中 \(f[i][j]\) 表示 \(i\) 向上走 \(2^j\) 步到达的节点
-
- \(dp[0][y][i]=dp[0][y][i-1]\)
-
- \(dp[1][y][i]=max(dp[1][y][i-1],dp[1][f[y][i-1]][i-1])\)
-
\(dp[0][y][i-1]\neq dp[0][f[y][i-1]][i-1]\)
-
- \(dp[0][y][i]=max(dp[0][y][i-1],dp[0][f[y][i-1]][i-1])\)
-
- \(dp[1][y][i]=max(\{min(dp[0][y][i-1],dp[0][f[y][i-1]][i-1]),dp[1][y][i-1],dp[1][f[y][i-1]][i-1]\})\)
分析:整体最大值为前后两部分的最大值,当前后两部分的最大值相等时,次大值为前后两部分次大值的较大值;否则为前后两部分最大值的较小值和前后两部分的次大值的最大值
- \(dp[1][y][i]=max(\{min(dp[0][y][i-1],dp[0][f[y][i-1]][i-1]),dp[1][y][i-1],dp[1][f[y][i-1]][i-1]\})\)
最后求最小生成树两点的最大和次大值可类比LCA
- 时间复杂度:\(O(m(logn+logm))\)
代码
// Problem: 次小生成树
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/description/358/
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
// #define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=3e5+5;
const LL inf=1e16;
int n,m,fa[N],d[N],f[N][20];
LL dp[2][N][20],val1,val2,res,ret;
bool v[N];
vector<PII> adj[N];
struct T
{
int x,y,z;
bool operator<(const T &t)
{
return z<t.z;
}
}tr[N];
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
void kruskal()
{
sort(tr+1,tr+1+m);
for(int i=1;i<=m;i++)
{
int x=tr[i].x,y=tr[i].y,z=tr[i].z;
int a=find(x),b=find(y);
if(a==b)continue;
res+=z;
adj[x].pb({y,z});
adj[y].pb({x,z});
v[i]=true;
fa[a]=b;
}
}
void bfs()
{
d[1]=0;
queue<int> q;
q.push(1);
while(q.size())
{
int x=q.front();
q.pop();
int len=log2(d[x]);
for(auto t:adj[x])
{
int y=t.fi,z=t.se;
if(y==f[x][0])continue;
q.push(y);
f[y][0]=x;
d[y]=d[x]+1;
dp[0][y][0]=z,dp[1][y][0]=-inf;
for(int i=1;i<=len;i++)
{
f[y][i]=f[f[y][i-1]][i-1];
if(dp[0][y][i-1]==dp[0][f[y][i-1]][i-1])
{
dp[0][y][i]=dp[0][y][i-1];
dp[1][y][i]=max(dp[1][y][i-1],dp[1][f[y][i-1]][i-1]);
}
else
{
dp[0][y][i]=max(dp[0][y][i-1],dp[0][f[y][i-1]][i-1]);
dp[1][y][i]=max({min(dp[0][y][i-1],dp[0][f[y][i-1]][i-1]),dp[1][y][i-1],dp[1][f[y][i-1]][i-1]});
}
}
}
}
}
void update(int x)
{
if(val1<x)val2=val1,val1=x;
else if(val2<x&&x!=val1)val2=x;
}
void lca(int x,int y)
{
val1=val2=-inf;
if(d[x]>d[y])swap(x,y);
while(d[x]<d[y])
{
int len=log2(d[y]-d[x]);
update(dp[0][y][len]);
update(dp[1][y][len]);
y=f[y][len];
}
if(x==y)return ;
for(int len=log2(d[x]);len>=0;len--)
if(f[x][len]!=f[y][len])
{
update(dp[0][x][len]);
update(dp[1][x][len]);
update(dp[0][y][len]);
update(dp[1][y][len]);
x=f[x][len];
y=f[y][len];
}
update(dp[0][x][0]);
update(dp[1][x][0]);
update(dp[0][y][0]);
update(dp[1][y][0]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
tr[i]={x,y,z};
}
kruskal();
bfs();
ret=inf;
for(int i=1;i<=m;i++)
{
if(v[i])continue;
lca(tr[i].x,tr[i].y);
if(tr[i].z==val1)
ret=min(ret,res-val2+tr[i].z);
else
ret=min(ret,res-val1+tr[i].z);
}
printf("%lld",ret);
return 0;
}