【Description】
小C最近学了很多最小生成树的算法,Prim算法、Kruskal算法、消圈算法 等等。正当小C洋洋得意之时,小P又来泼小C冷水了。小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是EM,严格次小生成树选择的边集是ES,那么需要满足:(value(e)表示边e的权值)
这下小C 蒙了,他找到了你,希望你帮他解决这个问题。
【Input】
第一行包含两个整数N和M,表示无向图的点数与边数。接下来M行,每行3个数x y z表示,点x和点y之间有一条边,边的权值为z。
【Output】
包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)
【Sample Input】
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
【Sample Output】
11
【HINT】
数据中无向图无自环;
50% 的数据N≤2 000 M≤3 000;
80% 的数据N≤50 000 M≤100 000;
100% 的数据N≤100 000 M≤300 000 ,边权值非负且不超过 10^9。
思路:先用Kruskal算法生成最小生成树,得到权值和sum,并标记在树中的边。然后枚举不在树中的边,加上任意一条边(u,v)后,最小生成树就会形成一条回路。在这条回路上找一条小于(u,v)权值的最大边,将它删除,就生成一颗次小生成树,则新生成树的权值和tmp=sum-t+w(u,v).最后将所有的tmp比较找到最小的极为严格次小生成树的权值和。
由此我们得到“严格次小生成树”的算法流程:
Step1、得到图的最小生成树,并得到权值和为sum;
Step2、枚举不是最小生成树的边(u,v),在最小生成树中查找u->v路径上小于 w(u,v)的最大边t(最近公共祖先),则新生成树的权值和tmp=sum-t+w(u,v);
Step3、从所有的tmp中选择一个最小的为严格次小生成树的权值和 ans=min(ans,tmp);
/*
Name: Tree
Copyright: Twitter & Instagram @stevebieberjr
Author: @stevebieberjr
Date: 12-07-16 15:02
*/
#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#define LL long long
#define maxn 300005
using namespace std;
int N,M,pa[maxn];
bool vis[maxn],used[maxn];
int dep[maxn],dist[maxn],fa[maxn]; //注意fa数组与pa数组有所区分
vector<int>g[maxn],w[maxn];
void DFS(int i) //DFS得到最小生成树各节点的fa[i]父亲结点,dep[i]深度,dist[i]到根结点的距离
{
vis[i]=true;
for(int k=0;k<g[i].size();k++)
{
int j=g[i][k],c=w[i][k];
if(vis[j]) continue;
fa[j]=i;
dep[j]=dep[i]+1;
dist[j]=dist[i]+c;
DFS(j);
}
}
struct edge
{
long long u,v,w;
};
vector<edge>E; //结构体+变长数组存储无向图的边
bool cmp(edge a,edge b)
{
return a.w<b.w;
} //根据边的权值由小到大排序
void initial(int n)
{
for(int i=0;i<=n;i++)
{
pa[i]=i;
}
} //将图清为空边图
int find(int i)
{
if(pa[i]==i) return i;
int root=find(pa[i]);
pa[i]=root;
return root;
} //找i结点的根结点
void Union(int x, int y)
{
int px=find(x);
int py=find(y);
pa[px]=py;
} //并集
bool judge(int x,int y)
{
int px=find(x);
int py=find(y);
return px==py;
} //检查x结点与y结点是否在同一个集合里
long long kruskal()
{
sort(E.begin(),E.end(),cmp);
initial(N);
LL sum=0;
for(int k=0;k<E.size();k++)
{
int x=E[k].u,y=E[k].v;
if(judge(x,y)) continue;
Union(x,y);
sum+=E[k].w;
g[x].push_back(y);
g[y].push_back(x);
w[x].push_back(E[k].w);
w[y].push_back(E[k].w); //树的存储结构
used[k]=1; //将在最小生成树中的边进行标记
}
return sum;
} //Kruskal演算法找最小生成树
int getmax(int x,int y,int z)
{
int maxv=0;
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y])
{
if(dist[x]-dist[fa[x]]<z)
{
maxv=max(maxv,dist[x]-dist[fa[x]]);
}
x=fa[x];
}
while(x!=y)
{
if(dist[x]-dist[fa[x]]<z)
{
maxv=max(maxv,dist[x]-dist[fa[x]]);
}
if(dist[y]-dist[fa[y]]<z)
{
maxv=max(maxv,dist[y]-dist[fa[y]]);
}
x=fa[x];
y=fa[y];
}
return maxv;
} //找x,y到最近公共祖先上的小于增加的边的权值的最大边
void solve()
{
LL sum=kruskal(); //得到最小生成树
DFS(1); //生成最小生成树的各种数组
LL ans=(long long)100000*1000000000;
for(int k=0;k<E.size();k++)
{
if(used[k]) continue; //如果是最小生成树的边,则忽略
int u=E[k].u,v=E[k].v;
int t=getmax(u,v,E[k].w); //计算LCA(u,v)同时计算u->v路径上小于e[k].w的最大边权
if(t<0) continue; //如果没找到,则忽略
ans=min(ans,sum-t+E[k].w); //计算新树的权值和sum-t+E[k].w
}
cout<<ans<<endl;
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d",&N,&M);
int x,y,z;
for(int i=1;i<=M;i++)
{
scanf("%d%d%d",&x,&y,&z);
E.push_back((edge){x,y,z});
}
solve();
return 0;
}