浅谈最小生成树
部分参考:https://oi-wiki.org/graph/mst/
Part 1.一些定义
定义一张图的生成树为从这张图中选择若干条边使这些边构成一棵树。
MST 即最小生成树,为一张图所有生成树中最小的。
Part 2.如何求 MST?
P3366 【模板】最小生成树
Prim 算法
维护一个点集,每次选择在点集之外,距离点集上一点距离最小的一个结点,加入点集,用新的边更新其他结点的距离。
以这张图为例:

过程:
- 开始只有点 \(1\) 在点集。边权和为 \(0\)。
- 加入 \(5\) 代价低,将它加入点集,增加边权和,为 \(10\)。
- 此时 \(2,4\) 一样近。任选一个,选 \(2\)。边权和加上 \(15\)。
- 最近的是 \(4\),加入。边权和加 \(15\)。
- 最近的是 \(5\),加入,边权和加 \(10\)。
- 最终边权和为 \(10+15+15+10=50\)。
注意到以上过程有点像 Dijkstra,同样可以堆优化。
正确性证明:
从任意一个结点开始,将结点分成两类:已加入的,未加入的。
每次从未加入的结点中,找一个与已加入的结点之间边权最小值最小的结点。
然后将这个结点加入,并连上那条边权最小的边。
重复 \(n-1\) 次即可。
证明:还是说明在每一步,都存在一棵最小生成树包含已选边集。
基础:只有一个结点的时候,显然成立。
归纳:如果某一步成立,当前边集为 \(F\),属于 \(T\) 这棵 MST,接下来要加入边 \(e\)。
如果 \(e\) 属于 \(T\),那么成立。
否则考虑 \(T+e\) 中环上另一条可以加入当前边集的边 \(f\)。
首先,\(f\) 的权值一定不小于 \(e\) 的权值,否则就会选择 \(f\) 而不是 \(e\) 了。
然后,\(f\) 的权值一定不大于 \(e\) 的权值,否则 \(T+e-f\) 就是一棵更小的生成树了。
因此,\(e\) 和 \(f\) 的权值相等,\(T+e-f\) 也是一棵最小生成树,且包含了 \(F\)。
我不会告诉你我是贺的。
Kruskal 算法
将边从小到大尝试加入,若这条边两端点在同一连通块,则舍弃。否则加入。
就以上图为例:
- 加入 \([1,5]\),成功。
- 加入 \([3,4]\),成功。
- 加入 \([1,2]\),成功。
- 加入 \([4,5]\),成功。
- 此时已有 \(4\) 条边,无需再加。
正确性证明:
思路很简单,为了造出一棵最小生成树,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了 \(n-1\) 条边,即形成了一棵树。
证明:使用归纳法,证明任何时候算法选择的边集都被某棵 MST 所包含。
基础:对于算法刚开始时,显然成立(最小生成树存在)。
归纳:假设某时刻成立,当前边集为 \(F\),令 \(T\) 为这棵 MST,考虑下一条加入的边 \(e\)。
如果 \(e\) 属于 \(T\),那么成立。
否则,\(T+e\) 一定存在一个环,考虑这个环上不属于 \(F\) 的另一条边 \(f\)(一定只有一条)。
首先,\(f\) 的权值一定不会比 \(e\) 小,不然 \(f\) 会在 \(e\) 之前被选取。
然后,\(f\) 的权值一定不会比 \(e\) 大,不然 \(T+e-f\) 就是一棵比 \(T\) 还优的生成树了。
所以,\(T+e-f\) 包含了 \(F\),并且也是一棵最小生成树,归纳成立。
我不会告诉你我是贺的。
代码:
以下代码使用 Kruskal。
#include<bits/stdc++.h>
using namespace std;
struct K{int a,b,c;}x[201011];
int rd(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,sum,coun,fa[5555];
int father(int x){if(fa[x]==0)return x;else return fa[x]=father(fa[x]);}
int cmp(K s,K b){return s.c<b.c;}
int main(){
n=rd();m=rd();
for(int i=1;i<=m;++i){
x[i].a=rd();x[i].b=rd();x[i].c=rd();
}
sort(x+1,x+m+1,cmp);
for(int i=1;i<=m;++i){
int a=father(x[i].a),b=father(x[i].b);
if(a!=b){
fa[a]=b;sum+=x[i].c;++coun;if(coun==n-1){
cout<<sum;return 0;
}
}
}
cout<<"orz";
}
以下代码使用 Prim(非堆优化)。这是复制题解的。
#include<bits/stdc++.h>
using namespace std;
#define re register
#define il inline
il int read()
{
re int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
#define inf 123456789
#define maxn 5005
#define maxm 200005
struct edge
{
int v,w,next;
}e[maxm<<1];
int head[maxn],dis[maxn],cnt,n,m,tot,now=1,ans;
bool vis[maxn];
il void add(int u,int v,int w){
e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
}
il void init(){
n=read(),m=read();
for(re int i=1,u,v,w;i<=m;++i){
u=read(),v=read(),w=read();
add(u,v,w),add(v,u,w);
}
}
il int prim(){
for(re int i=2;i<=n;++i){dis[i]=inf;}
for(re int i=head[1];i;i=e[i].next){
dis[e[i].v]=min(dis[e[i].v],e[i].w);
}
while(++tot<n){
re int minn=inf;
vis[now]=1;
for(re int i=1;i<=n;++i){
if(!vis[i]&&minn>dis[i]){
minn=dis[i];
now=i;
}
}
ans+=minn;
for(re int i=head[now];i;i=e[i].next){
re int v=e[i].v;
if(dis[v]>e[i].w&&!vis[v]){
dis[v]=e[i].w;
}
}
}
return ans;
}
int main(){
init();printf("%d",prim());
return 0;
}
堆优化 Prim:这也是复制题解的。
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define R register int
using namespace std;
int k,n,m,cnt,sum,ai,bi,ci,head[5005],dis[5005],vis[5005];
struct Edge{
int v,w,next;
}e[400005];
void add(int u,int v,int w){
e[++k].v=v;e[k].w=w;e[k].next=head[u];head[u]=k;
}
typedef pair <int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> > q;
void prim(){
dis[1]=0;
q.push(make_pair(0,1));
while(!q.empty()&&cnt<n){
int d=q.top().first,u=q.top().second;
q.pop();
if(vis[u]) continue;
cnt++;
sum+=d;
vis[u]=1;
for(R i=head[u];i!=-1;i=e[i].next)
if(e[i].w<dis[e[i].v])dis[e[i].v]=e[i].w,q.push(make_pair(dis[e[i].v],e[i].v));
}
}
int main()
{
memset(dis,127,sizeof(dis));
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(R i=1;i<=m;i++)
{
scanf("%d%d%d",&ai,&bi,&ci);
add(ai,bi,ci);
add(bi,ai,ci);
}
prim();
if(cnt==n)printf("%d",sum);
else printf("orz");
}
Part 3.经典例题
P1194 买礼物
将每一件东西转化为一个节点,若 \(K_{i,j}\ne 0\),则连一条连接 \(i,j\),边权为 \(K_{i,j}\) 的边。
在虚构一个节点,将这个节点向每个节点连边,边权为 \(A\)。
求出 MST,即为答案。
#include<bits/stdc++.h>
using namespace std;
int rd(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a=rd(),n=rd(),c[555][555],L,k,dian[555],join[555],sum;
main(){
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j){
c[i][j]=rd();if(c[i][j]==0)c[i][j]=999999999;
}
for(int i=1;i<=n;++i)c[0][i]=a;
memset(join,127,sizeof(join));join[0]=0;
for(int i=0;i<=n;++i){
k=999999999;
for(int j=0;j<=n;++j)if(!dian[j]&&join[j]<k){k=join[j];L=j;}
dian[L]=1;sum+=k;
for(int j=1;j<=n;++j)join[j]=min(join[j],c[L][j]);
}
printf("%d",sum);
}
P4047 [JSOI2010] 部落划分
这题就是一个最小生成树,不同的是,一共有 \(k\) 个部落,我们只需要选择最小的 \(n-k\) 条边,接下来选的边就是答案。
给个核心代码:
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;++i){
int a=fa(e[i].a),b=fa(e[i].b);
if(a!=b){
father[a]=b;++coun;
if(coun==n-k+1){
printf("%.2lf",x[i].e);return 0;
}
}
}
P1195 口袋的天空
与上个例题相似,连成 \(k\) 个棉花糖,需要选 \(n-k\) 条边。
代码不给了。
P1396 营救
这道题做法很多,用最短路也能解。这里使用 MST 思想。
只要让 \(s,t\) 连接,故从小到大选边,像 Kruskal 一样加边,若 \(s,t\) 连通,答案为目前选的边权。
sort(x+1,x+m+1,cmp);
for(int i=1;i<=m;++i){
int a=father(x[i].a),b=father(x[i].b);
if(a!=b){
fa[a]=b;if(father(s)==father(t)){
printf("%d",x[i].c);return 0;
}
}
P3101 [USACO14JAN] Ski Course Rating G
利用 MST 思想,每次合并时,若合并后的集合大小超过 \(k\),且有 \(x\) 个起点,则对答案造成 \(x\times v\) 的贡献,其中 \(v\) 为边权,随后将该集合中起点数变为 \(0\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace io{
inline int r(){
int Q=1,A=0;char C=getchar();while(C<'0'||C>'9'){if(C=='-')Q=-1;C=getchar();}
while(C>='0'&&C<='9'){A=(A<<1)+(A<<3)+(C^48);C=getchar();}return A*Q;
}
inline void WR(int L){if(L<0){L=-L;putchar('-');}if(L==0)return;WR(L/10);putchar(L%10+48);}
inline void wln(int N){if(N==0)putchar('0');else WR(N);putchar('\n');}
inline void ww(int N){if(N==0)putchar('0');else WR(N);putchar(' ');}
inline void w(int N){if(N==0)putchar('0');else WR(N);}
}
using namespace io;
namespace BD{
int n,m,a[502][502],Ans,fa[290000],h[290000],b[290000],v[290000],D,C;
struct e{int f,t,le;}w[1919810];
inline bool Cmp(e x,e y){return x.le<y.le;}
inline int Fa(int L){return fa[L]==L?L:Fa(fa[L]);}
inline void meg(int x,int y,int Z){
x=Fa(x),y=Fa(y);
if(x!=y){
int hx=h[x],hy=h[y];
if(hx<hy){
fa[x]=y;v[y]+=v[x];h[y]=max(h[y],h[x]+1);b[y]+=b[x];if(v[y]>=D){Ans+=Z*b[y];b[y]=0;};
}else{
fa[y]=x;v[x]+=v[y];h[x]=max(h[x],h[y]+1);b[x]+=b[y];if(v[x]>=D){Ans+=Z*b[x];b[x]=0;};
}
}
}
inline int T(int x,int y){return m*(x-1)+y;}
inline void _(){
n=r(),m=r(),D=r();
for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){
a[i][j]=r();
if(i!=1){++C;w[C].f=T(i-1,j);w[C].t=T(i,j);w[C].le=abs(a[i][j]-a[i-1][j]);}
if(j!=1){++C;w[C].f=T(i,j-1);w[C].t=T(i,j);w[C].le=abs(a[i][j-1]-a[i][j]);}
}
sort(w+1,w+1+C,Cmp);
for(int i=1,P;i<=n;++i)for(int j=1;j<=m;++j){
P=r();
b[T(i,j)]=P;h[T(i,j)]=1;fa[T(i,j)]=T(i,j);v[T(i,j)]=1;
}
for(int i=1;i<=C;++i)meg(w[i].f,w[i].t,w[i].le);wln(Ans);
}
}
signed main(){BD::_();}
Part 4.特殊图的 MST
P2573 [SCOI2012] 滑雪
第一问 DFS 即可。
第二问需要把第一问中所有有用的边记录下来,问题转化为有向图的 MST。
使用 Kruskal 算法时,需改变排序方式,具体的,先按终点的 \(h\) 排序,再按边权排序。
其他部分与正常的 Kruskal 一样。
证明的话,这样能使遍历的点从大到小,保证正确性。
#include<bits/stdc++.h>
#define int long long
#define ri register int
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
using namespace std;
namespace IO{
char ibuf[(1<<20)+1],*iS,*iT;
#if ONLINE_JUDGE
#define gh() (iS==iT?iT=(iS=ibuf)+fread(ibuf,1,(1<<20)+1,stdin),(iS==iT?EOF:*iS++):*iS++)
#else
#define gh() getchar()
#endif
inline int r(){
int o=1,p=0;
char q=gh();
while(q<'0'||q>'9'){if(q=='-')o=-1;q=gh();}
while(q>='0'&&q<='9'){p=(p<<1)+(p<<3)+(q^48);q=gh();}
return o*p;
}
inline char gc(){char q=gh();while(q<=' ')q=gh();return q;}
inline string gs(){string s="";char g=gh();while(g<=' ')g=gh();while(g>' '){s+=g;g=gh();}return s;}
inline void Wi(int _){
if(_==0)return;if(_<0){_=-_;putchar('-');}
Wi(_/10);putchar((_%10)^48);
}
inline void wln(int J){if(J==0)putchar('0');else Wi(J);putchar('\n');}
inline void w(int J){if(J==0)putchar('0');else Wi(J);}
inline void ww(int J){if(J==0)putchar('0');else Wi(J);putchar(' ');}
}
using namespace IO;
namespace D1n0{
struct E{
int u,v,dis;
}e[1919810];
int m,fa[114514],n,h[114514],Su,vis[114514],cnt;
bool operator<(E a,E b){
return (h[a.v]!=h[b.v])?h[a.v]>h[b.v]:a.dis<b.dis;
}
inline int father(int X){
if(fa[X]==X)return X;
return fa[X]=father(fa[X]);
}
vector<pii>v[114514];
inline void dfs(int a){
if(vis[a])return;vis[a]=1;++Su;
for(pii p:v[a]){
++cnt;
e[cnt].u=a,e[cnt].v=p.fi;e[cnt].dis=p.se;
dfs(p.fi);
}
}
inline int mst(){
for(ri i=1;i<=n;++i)fa[i]=i;
sort(e+1,e+1+cnt);
int Ans=0;
for(ri i=1;i<=cnt;++i){
int x=e[i].u,y=e[i].v,Dis=e[i].dis;
x=father(x);y=father(y);
if(x!=y){
fa[x]=y;
Ans+=Dis;
}
}
return Ans;
}
inline void zyt(){
n=r(),m=r();for(ri i=1;i<=n;++i)h[i]=r();
for(ri i=1;i<=m;++i){
int a=r(),b=r(),s=r();
if(h[a]>=h[b])v[a].pb(mp(b,s));
if(h[a]<=h[b])v[b].pb(mp(a,s));
}
dfs(1);ww(Su);wln(mst());
}
}
signed main(){
D1n0::zyt();
}
P5687 [CSP-S2019 江西] 网格图
网格图的 MST。
考虑 Kruskal 的实质。先选短边加入。因此先将 \(a,b\) 排序。
考虑以下情况:选了 \(a\) 条横向边,\(b\) 条纵向边,如果要加横向边或纵向边,要加几条?
手模一下发现,要加 \(min(m-1,m-b)\) 条横向边,\(min(n-1,n-a)\) 条纵向边。
按 \(a,b\) 中的顺序从小到大加入。
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace IO{
inline int r(){
int z=1,y=0;char t=getchar();while(t<'0'||t>'9'){if(t=='-')z=-1;t=getchar();}
while(t<='9'&&t>='0'){y=(y<<1)+(y<<3)+(t^48);t=getchar();}return z*y;
}
inline void w_(int x){if(x<0){x=-x;putchar('-');}if(x==0)return;w_(x/10);putchar(x%10+48);}
inline void wln(int x){if(x==0)putchar('0');else w_(x);putchar('\n');}
inline void ww(int x){if(x==0)putchar('0');else w_(x);putchar(' ');}
inline void w(int x){if(x==0)putchar('0');else w_(x);}
}
using namespace IO;
namespace D1n0{
int n,m,a[655555],b[655555];
inline void _(){
n=r(),m=r();
for(int i=1,x;i<=n;++i)a[i]=r();
for(int i=1,x;i<=m;++i)b[i]=r();
sort(a+1,a+1+n);sort(b+1,b+1+m);
int t1=m-1,t2=n-1,S=a[1]*(m-1)+b[1]*(n-1);
for(int i=2,j=2;i<=n&&j<=m;){
if(a[i]<b[j])S+=a[i]*t1,--t2,++i;
else S+=b[j]*t2,--t1,++j;
}
wln(S);
}
}
signed main(){D1n0::_();}
P6171 [USACO16FEB] Fenced In G
网格图 MST 经典应用。题解详见 https://www.luogu.com.cn/article/0nu3ldlw。
P12375 「LAOI-12」MST?
完全图的 MST。
显然,最佳状态是:每次构成完全图后加入边。
易得这样加入的边为 \(a={1,2,4,7,11,16,22}\)(后略),为 \(x=0.5 i^2-0.5 i + 1\)。
\(\sum _ {i=1}^{n}a_i=\dfrac{(2n+1)(n+1)n} 12 - \dfrac{(n+1)n} 4 +n=\dfrac{(n^2+5)n} 6\)。
设加入 \(x\) 条边后剩余边无法再次构成完全图,则有 \(m-a_x \ne n-x-1\)。
\(0.5 x^2-1.5x-m+n \le 0\)。
答案即为 \(x\) 个点构成的完全图的 MST 边权和加上剩余边边权之和。
用求根公式求出 \(x\),则答案为 \(\dfrac{(x^2+5)x} 6 +\dfrac {(2m-n+x+2)(n-x-1)} 2\)。
数据大,用 __int128 计算再取模即可。
#include<bits/stdc++.h>
#define int __int128
#define ri register int
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
using namespace std;
const int p=1e9+7;
namespace IO{
char ibuf[(1<<20)+1],*iS,*iT;
#if ONLINE_JUDGE
#define gh() (iS==iT?iT=(iS=ibuf)+fread(ibuf,1,(1<<20)+1,stdin),(iS==iT?EOF:*iS++):*iS++)
#else
#define gh() getchar()
#endif
inline int r(){
int o=1,p=0;
char q=gh();
while(q<'0'||q>'9'){if(q=='-')o=-1;q=gh();}
while(q>='0'&&q<='9'){p=(p<<1)+(p<<3)+(q^48);q=gh();}
return o*p;
}
inline char gc(){char q=gh();while(q<=' ')q=gh();return q;}
inline string gs(){string s="";char g=gh();while(g<=' ')g=gh();while(g>' '){s+=g;g=gh();}return s;}
inline void Wi(int _){
if(_==0)return;if(_<0){_=-_;putchar('-');}
Wi(_/10);putchar((_%10)^48);
}
inline void wln(int J){if(J==0)putchar('0');else Wi(J);putchar('\n');}
inline void w(int J){if(J==0)putchar('0');else Wi(J);}
inline void ww(int J){if(J==0)putchar('0');else Wi(J);putchar(' ');}
}
using namespace IO;
namespace D1n0{
int T,m,n;
inline void _(){
T=r();
while(T--){
n=r();m=r();
int a=sqrtl(2.25+2*(m-n))+1.5;
wln((a*(a*a+5)/6+(2*m-n+a+2)*(n-a-1)/2)%998244353);
}
}
}
signed main(){D1n0::_();}
Part 5.次小生成树
P4180 [BJWC2010] 严格次小生成树
首先,已知 \(a,b\) 的严格最小和严格次小,如何维护连接 \(a,b\) 后数组的严格次小值?
设 \(a\) 的严格最小为 \(a1\),严格次小为 \(a2\);\(b\) 的严格最小为 \(b1\),严格次小为 \(b2\)。
则严格最小为 \(min(a1,b1)\),将 \(a1,a2,b1,b2\) 中等于 \(min(a1,b1)\) 的改成 \(-\operatorname{inf}\),剩余最小值为严格次小值。
用 pair 维护的话(前者最小,后者严格次小),代码为:
#define pii pair<int,int>
inline pii mg(pii a,pii b){
int A=a.fi,B=a.se,C=b.fi,D=b.se;
int M=max(max(A,B),max(C,D));
pii c;c.fi=M;
if(A==M)A=-1e18;if(B==M)B=-1e18;if(C==M)C=-1e18;if(D==M)D=-1e18;
c.se=max(max(A,B),max(C,D));
return c;
}
下面进入正题。
有个性质就是次小生成树与最小生成树只有一条边不同,那么就枚举加入哪条边。然后,删去原路径上的权值最大的一条边。当然,由于是严格次小,若权值最大的一条边与加入边边权相同,则加入路径上的严格次短边。可以用倍增或树剖维护。
注意有自环。
代码比较难写,坑点多,以下是我 \(108\) 行 \(2912\) 字节代码:
#include<bits/stdc++.h>
#define int long long
#define ri register int
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
using namespace std;
namespace IO{
char ibuf[(1<<20)+1],*iS,*iT;
#if ONLINE_JUDGE
#define gh() (iS==iT?iT=(iS=ibuf)+fread(ibuf,1,(1<<20)+1,stdin),(iS==iT?EOF:*iS++):*iS++)
#else
#define gh() getchar()
#endif
inline int r(){
int o=1,p=0;
char q=gh();
while(q<'0'||q>'9'){if(q=='-')o=-1;q=gh();}
while(q>='0'&&q<='9'){p=(p<<1)+(p<<3)+(q^48);q=gh();}
return o*p;
}
inline char gc(){char q=gh();while(q<=' ')q=gh();return q;}
inline string gs(){string s="";char g=gh();while(g<=' ')g=gh();while(g>' '){s+=g;g=gh();}return s;}
inline void Wi(int _){
if(_==0)return;if(_<0){_=-_;putchar('-');}
Wi(_/10);putchar((_%10)^48);
}
inline void wln(int J){if(J==0)putchar('0');else Wi(J);putchar('\n');}
inline void w(int J){if(J==0)putchar('0');else Wi(J);}
inline void ww(int J){if(J==0)putchar('0');else Wi(J);putchar(' ');}
}
using namespace IO;
namespace D1n0{
struct E{
int u,v,dis;
}e[1919810];
int m,fa[114514],n,p[1919810],S,kfa[114514][20],dep[114514];
pii t[114514][20];
inline pii mg(pii a,pii b){
int A=a.fi,B=a.se,C=b.fi,D=b.se;
int M=max(max(A,B),max(C,D));
pii c;c.fi=M;
if(A==M)A=-1e18;if(B==M)B=-1e18;if(C==M)C=-1e18;if(D==M)D=-1e18;
c.se=max(max(A,B),max(C,D));
return c;
}
bool operator<(E a,E b){
return a.dis<b.dis;
}
vector<pii>ve[114514];
inline int father(int X){
if(fa[X]==X)return X;
return fa[X]=father(fa[X]);
}
inline void mst(){
for(ri i=1;i<=n;++i)fa[i]=i;
sort(e+1,e+1+m);
int Ans=0;
for(ri i=1;i<=m;++i){
int x=e[i].u,y=e[i].v,Dis=e[i].dis;
x=father(x);y=father(y);
if(x!=y){
p[i]=1;S+=Dis;
fa[x]=y;
ve[e[i].u].pb(mp(e[i].v,Dis));ve[e[i].v].pb(mp(e[i].u,Dis));
}
}
}
inline void dfs(int x,int y){
dep[x]=dep[y]+1;
for(ri i=1;i<=19;++i)kfa[x][i]=kfa[kfa[x][i-1]][i-1],
t[x][i]=mg(t[x][i-1],t[kfa[x][i-1]][i-1]);
for(auto it:ve[x])if(it.fi!=y){
kfa[it.fi][0]=x;t[it.fi][0]=mp(it.se,-1e18);
dfs(it.fi,x);
}
}
inline pii get(int x,int y){
if(dep[x]<dep[y])swap(x,y);
pii a=mp(-1e18,-1e18);
for(ri i=19;i>=0;--i)if(dep[kfa[x][i]]>=dep[y])a=mg(a,t[x][i]),x=kfa[x][i];
if(x==y)return a;
for(ri i=19;i>=0;--i)if(kfa[x][i]!=kfa[y][i])a=mg(mg(a,t[x][i]),t[y][i]),x=kfa[x][i],y=kfa[y][i];
return mg(a,mg(t[x][0],t[y][0]));
}
inline void zyt(){
n=r(),m=r();
for(ri i=1;i<=m;++i){
int a=r(),b=r(),s=r();
e[i]=(E){a,b,s};
}
mst();
dfs(1,0);
int Ans=1e18;
for(ri i=1;i<=m;++i)
if(!p[i]&&e[i].u!=e[i].v){
pii a=get(e[i].u,e[i].v);
if(a.fi==e[i].dis)Ans=min(Ans,S+e[i].dis-a.se);
else Ans=min(Ans,S+e[i].dis-a.fi);
}
w(Ans);
}
}
signed main(){
D1n0::zyt();
}
GL&HF!
浙公网安备 33010602011771号