分为非严格次小生成树和严格次小生成树
对于前者,若最小生成树不唯一
则次小生成树与最小生成树权值相同
对于后者,则要求次小生成树权值严格大于最小生成树
接下来的求解方法都将分别讨论
这里是次小生成树的版题
这一算法较简洁易懂,且代码量小
但算法时间复杂度较高,一般不建议用,了解思路即可
效率较高的算法参见算法二
首先,不难证明次小生成树的连边与最小生成树一定不相同
因此,我们可以枚举每一条在最小生成树上的边
在剩下的边的集合中再求最小生成树
也就是再对n-1个缺一条边的图求最小生成树
对于严格次小生成树
找出n-1棵树中找到权值>原最小生成树且最小的
对非严格次小生成树
找出n-1棵树中找到权值>=原最小生成树且最小的
这种思路可以说是很暴力了
代码就不贴了,主要讲下面的高效算法
这一算法代码量较大,维护方法不唯一
思路不难,主要在于维护方法的选择,特别是对于严格次小生成树
算法时间复杂度是很高效的(也要看维护方法)
对于一棵已经求出的最小生成树
枚举每一条不在最小生成树上的边
并把这条边加入最小生成树
设这条边连接的两个节点为u和v
这时树上u到v的路径会出现回路
所以我们需要删掉u到v路径上的一条边使其重新变成一颗树
这时生成树权值的增量就是新加入的边权-删掉的边权
为了使增量最小,我们要删去的自然是u到v路径中权值最大的边
特别的,若要求的是严格次小生成树
如果发现u到v路径中权值最大的边等于加入的边
就要删掉u到v路径上的次大边权
对于树上路径权值的维护选择有很多
例如树剖、LCT、倍增等等
这里用倍增示例
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long lt;
lt read()
{
lt f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=100010;
lt n,m,ans;
struct node{int u,v;lt dis;}edge[maxn*10];
struct node2{lt v,dis,nxt;}E[maxn*10];
int head[maxn],tot;
int fa[maxn];
bool judge[maxn];//判断是否是最小生成树的边
int gra[maxn][18],dep[maxn];
lt fir[maxn][18],sec[maxn][18];
//fir表示区间内最大值,sec表示次大值,和lca一样的倍增思想
bool cmp(node a,node b){return a.dis<b.dis;}
void add(int u,int v,lt dis)
{
E[++tot].nxt=head[u];
E[tot].v=v;
E[tot].dis=dis;
head[u]=tot;
}
int find(int x)
{
if(x==fa[x]) return x;
else return fa[x]=find(fa[x]);
}
lt kruskal()
{
int num=0; lt len=0;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int u=edge[i].u,v=edge[i].v;lt dis=edge[i].dis;
int fu=find(u),fv=find(v);
if(fu!=fv)
{
fa[fu]=fv; judge[i]=true;
num++; len+=dis;
add(u,v,dis); add(v,u,dis);
if(num==n-1) break;
}
}
return len;
}
void dfs(int u,int pa)
{
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(v==pa) continue;
dep[v]=dep[u]+1; gra[v][0]=u;
fir[v][0]=E[i].dis; sec[v][0]=-1e9;
dfs(v,u);
}
}
void work()
{
for(int i=1;(1<<i)<=n;i++)
for(int u=1;u<=n;u++)
{
gra[u][i]=gra[ gra[u][i-1] ][i-1];//处理祖先
fir[u][i]=max(fir[u][i-1],fir[ gra[u][i-1] ][i-1]);
sec[u][i]=max(sec[u][i-1],sec[ gra[u][i-1] ][i-1]);
//处理最大和次大值
if(fir[u][i-1]>fir[ gra[u][i-1] ][i-1])
sec[u][i]=max(fir[ gra[u][i-1] ][i-1],sec[u][i]);
else if(fir[u][i-1]<fir[ gra[u][i-1] ][i-1])
sec[u][i]=max(fir[u][i-1],sec[u][i]);
//注意对次大值的判断
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
int d=dep[u]-dep[v];
for(int i=0;(1<<i)<=d;i++)
if((1<<i)&d) u=gra[u][i];
if(u==v) return u;
for(int i=(int)log(n);i>=0;i--)
{
if(gra[u][i]!=gra[v][i])
u=gra[u][i],v=gra[v][i];
}
return gra[u][0];
}
lt qmax(int u,int v,lt dis)
{
lt tp=-1e9;
for(int i=1;(1<<i)<=n;i++)//倍增思想
{
//只要深度比LCA大,就倍增查询更新
if(dep[ gra[u][i] ]>=dep[v])
{
if(dis==fir[u][i]) tp=max(tp,sec[u][i]);
else tp=max(tp,fir[u][i]);//发现一样的边权要查询次大值
}
}
return tp;
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
edge[i].u=read(),edge[i].v=read(),edge[i].dis=read();
sort(edge+1,edge+1+m,cmp);
ans=kruskal();//先求最小生成树
dfs(1,-1);//无根树转有根树
work();//预处理gra,fir和sec
lt add=1e9;
for(int i=1;i<=m;i++)
{
if(judge[i])continue;//若是最小生成树的边就跳过
int u=edge[i].u,v=edge[i].v;lt dis=edge[i].dis;
int LCA=lca(u,v);
lt maxu=qmax(u,LCA,dis);
lt maxv=qmax(v,LCA,dis);//分别查询u、v到他们LCA路径上的最大值
add=min(add,dis-max(maxu,maxv));//更新最小增量
}
cout<<ans+add;
return 0;
}