【每一个弧相当于一个链,在图中画了,链上面有最大值和次大值(bfs倍增初始化好的),把这些所有的最大值和次大值存起来,找出整体的最大值和次大值】
与下链接同类型:
//新版代码
/*
要求严格次小生成树,
当然这也是模板题 .
涉及到严格次小生成树的问题,都要与最近公共祖先结合起来。
s1[i][j]:存i 跳2^j步路径上边权的最大值
s2[i][j]:存i 跳2^j步路径上边权的次大值
*/
/*
本题的基本模型是:
求树上两个点之间路径的最大边权和次大边权(利用LCA的那个)
*/
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=3e5+10;
const int inf=0x3f3f3f3f;
typedef long long LL;
int e[M],ne[M],h[N],w[M],idx;
int p[N];
int depth[N];
int fa[N][20];
int s1[N][20];
int s2[N][20];
int cnt;
int n,m;
struct Node
{
int x,y;
int w;
bool is_leaf;
bool operator<(const Node &W)const
{
return w<W.w;
}
}edges[M];
void add(int a,int b,int c)
{
e[idx]=b;
ne[idx]=h[a];
w[idx]=c;
h[a]=idx++;
}
void bfs(int root)
{
memset(depth,0x3f,sizeof depth);
depth[0]=0;
depth[root]=1;
queue<int> q;
q.push(1);
while(!q.empty())
{
auto 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;
q.push(j);
fa[j][0]=t;
s1[j][0]=w[i];
s2[j][0]=-inf;
for(int k=1;k<=19;k++)
{
fa[j][k]=fa[fa[j][k-1]][k-1];
//s1[j][k]=max(s1[j][k-1],s1[fa[j][k-1]][k-1]);
//s2[j][k]=max(s2[j][k-1],s2[fa[j][k-1]][k-1]);
//由于要更新最大值和次大值,所以拆开来写
s1[j][k]=s2[j][k]=-inf;
//先存下来所有的元素
int distance[5]={s1[j][k-1],s2[j][k-1],s1[fa[j][k-1]][k-1],s2[fa[j][k-1]][k-1]};
for(int u=0;u<4;u++)
{
int d=distance[u];
if(d>s1[j][k])
{
s2[j][k]=s1[j][k];
s1[j][k]=d;
}
else if(d!=s1[j][k]&&d>s2[j][k])
{
s2[j][k]=d;
}
}
}
}
}
}
}
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
int lca(int a,int b,int w)
{
static int distance[N*2]; //每条路径上面的 最大值 和 次大值 都存在distance中
/*
其次为什么最多有N*2个路径要保存呢?
其实这里不算路径,顶多是一个点跳到另外一个点的一道弧,
根据所画的图,每一个弧都可以看成一个链,有最大值和次大值
最多有N个点,所以最多N条弧,每个弧保存最大值和次大值,
就是 N*2
*/
cnt=0;
if(depth[a]<depth[b])
{
swap(a,b);
}
for(int k=19;k>=0;k--)
{
if(depth[fa[a][k]]>=depth[b])
{
distance[cnt++]=s1[a][k];//每跨一大步,相当于图中的弧度,把每个弧度的最大次大值都存起来
distance[cnt++]=s2[a][k];//每一根弧,都相当于图中的一个链,所以都要存起来
a=fa[a][k];
}
}
if(a!=b) //继续跳
{
for(int k=19;k>=0;k--)
{
if(fa[a][k]!=fa[b][k])
{
//①
distance[cnt++]=s1[a][k];
distance[cnt++]=s2[a][k];
//②
distance[cnt++]=s1[b][k];
distance[cnt++]=s2[b][k];
a=fa[a][k];
b=fa[b][k];
}
}
distance[cnt++]=s1[a][0];
distance[cnt++]=s1[b][0];
}
//下面从所有存的最大值和次大值里面选出最终的最大值和次大值
int maxv1=-inf,maxv2=-inf;
for(int i=0;i<cnt;i++)
{
int d=distance[i];
if(d>maxv1)
{
maxv2=maxv1;
maxv1=d;
}
else if(d!=maxv1&&d>maxv2)
{
maxv2=d;
}
}
if(w>maxv1) return w-maxv1;
if(w>maxv2) return w-maxv2;
return inf;
}
LL kruskal()
{
sort(edges+1,edges+1+m);
//memset(h,-1,sizeof h);
LL sum=0;
for(int i=1;i<=m;i++)
{
int a=edges[i].x;
int b=edges[i].y;
int w=edges[i].w;
int pa=find(a);
int pb=find(b);
if(pa!=pb)
{
p[pa]=pb;
edges[i].is_leaf=true;
sum+=w;
}
}
return sum;
}
void build()
{
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
if(edges[i].is_leaf)
{
int a=edges[i].x,b=edges[i].y,w=edges[i].w;
add(a,b,w);
add(b,a,w);
}
}
}
int main()
{
//int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
edges[i]={x,y,z};
}
for(int i=1;i<=n;i++)
{
p[i]=i;
}
LL sum=kruskal();
build();
bfs(1);
LL ans=1e18;
//要知道最小生成树的sum和次小生成树的sum只有一条边权不同
for(int i=1;i<=m;i++) //枚举一下所有边
{
if(!edges[i].is_leaf)
{
int a=edges[i].x;
int b=edges[i].y;
int w=edges[i].w;
ans=min(ans,sum+lca(a,b,w));
//lca返回的是:相当于新边-旧边的差值
}
}
printf("%lld\n",ans);
return 0;
}
次小生成树的原理:
本题的题解:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10,M=3e5+10;
const int inf=0x3f3f3f3f;
int n,m;
struct Edge
{
int a,b,w;
bool used; //标明当前边有没有被用过
bool operator<(const Edge &W)const
{
return w<W.w;
}
}edges[M];
int e[M],ne[M],h[N],w[M],idx;
int p[N];
int fa[N][18],d1[N][18],d2[N][18];
int q[N];
int depth[N];
int cnt;
void add(int a,int b,int c) //做最小生成树算法的时候,顺便建立最小生成树
{
e[idx]=b;
ne[idx]=h[a];
w[idx]=c;
h[a]=idx++;
}
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
LL kruskal()
{
for(int i=1;i<=n;i++) p[i]=i;
sort(edges,edges+m);
LL res=0;
for(int i=0;i<m;i++)
{
int a=find(edges[i].a);
int b=find(edges[i].b);
int w=edges[i].w;
if(a!=b)
{
p[a]=b;
res+=w;
edges[i].used=true;
}
}
return res;
}
void build()
{
memset(h,-1,sizeof h);
for(int i=0;i<m;i++)
{
if(edges[i].used)
{
int a=edges[i].a,b=edges[i].b,w=edges[i].w;
add(a,b,w);
add(b,a,w);
}
}
}
int lca(int a,int b,int w)
{
static int distance[N*2]; //每条路径上面的 最大值 和 次大值 都存在distance中、
/*
其次为什么最多有N*2个路径要保存呢?
其实这里不算路径,顶多是一个点跳到另外一个点的一道弧,
根据所画的图,每一个弧都可以看成一个链,有最大值和次大值
最多有N个点,所以最多N条弧,每个弧保存最大值和次大值,
就是 N*2
*/
cnt=0;
if(depth[a]<depth[b])
{
swap(a,b);
}
for(int k=16;k>=0;k--)
{
if(depth[fa[a][k]]>=depth[b]) //当跳k步深度还不够时
{
distance[cnt++]=d1[a][k]; //每跨一大步,相当于图中的弧度,把每个弧度的最大次大值都存起来
distance[cnt++]=d2[a][k]; //每一根弧,都相当于图中的一个链,所以都要存起来
a=fa[a][k];
}
}
if(a!=b) //继续跳
{
for(int k=16;k>=0;k--)
{
if(fa[a][k]!=fa[b][k])
{
distance[cnt++]=d1[a][k];
distance[cnt++]=d2[a][k];
distance[cnt++]=d1[b][k];
distance[cnt++]=d2[b][k];
a=fa[a][k];
b=fa[b][k];
}
}
//到目前为止只是跳到了最近公共祖先的下一层,所以还要再跳一层
distance[cnt++]=d1[a][0];
distance[cnt++]=d1[b][0];
}
//distance存的是集合,我们要在里面找出最大值和次大值
//记住了,最终返回的是一个差值,所以还要找出旧边中
int td1=-inf,td2=-inf;
for(int i=0;i<cnt;i++)
{
int d=distance[i];
if(d>td1) td2=td1,td1=d;
else if(d!=td1&&d>td2) td2=d;
}
if(w>td1) return w-td1; //判断如果可以用最大边
if(w>td2) return w-td2; //如果不可以用最大边,就用次大边
return inf; //否则返回+无穷大
}
void bfs(int root)
{
memset(depth,0x3f,sizeof depth); //和dis一样要初始化为0x3f
depth[0]=0;
depth[root]=1;
int hh=0,tt=-1;
q[++tt]=1;
while(hh<=tt)
{
int t=q[hh++];
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;
q[++tt]=j;
//下面就是预处理一下:三个倍增数组
//首先先把步长为0的都初始化了
fa[j][0]=t;
d1[j][0]=w[i];
d2[j][0]=-inf; //不存在次大边,所以设置为-无穷
for(int k=1;k<=16;k++)
{
int anc=fa[j][k-1]; //先存一下到达的点
fa[j][k]=fa[anc][k-1];
/*
难点在最大值和次大值的更新上
之前说了,最大值和次大值,就是在
四个值里面选,所以我们就在四个值
里面选就好了。
*/
int distance[5]={d1[j][k-1],d2[j][k-1],d1[anc][k-1],d2[anc][k-1]}; //存一下四个值
//然后就是在上面这一整个集合中求最大值和次大值
d1[j][k]=d2[j][k]=-inf; //先初始化为负无穷,因为要求最大和次大
for(int u=0;u<4;u++)
{
int d=distance[u];
if(d>d1[j][k])
{
d2[j][k]=d1[j][k];
d1[j][k]=d;
}
else if(d!=d1[j][k]&&d>d2[j][k])
{
d2[j][k]=d;
}
}
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
// memset(h,-1,sizeof h);
for(int i=0;i<m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
edges[i]={a,b,c};
}
LL sum=kruskal();
build(); //建那些 used被标记为true的边
bfs(1); //用bfs初始化三个倍增数组f[][18],d1[][18],d2[][18]
LL res=1e18;
for(int i=0;i<m;i++) //枚举一下非树边
{
if(!edges[i].used)
{
int a=edges[i].a,b=edges[i].b;
int w=edges[i].w;
res=min(res,sum+lca(a,b,w));
//lca返回的是类似于之前(o(n*n)版本的)的 加上 w(非树边) 并且 减去一个树边的边权
//相当于新边-旧边的差值
}
}
printf("%lld\n",res);
return 0;
}