定义
有严格次小以及非严格次小;
严格次小就是权值和必须比最小的小;
非严格次小,也就是说最小生成树不唯一;
方法
方法一解释
这个比较容易想到,每条边都去掉试试看,一定可以枚举完所有的解;
方法二解释
简单来说就是下面这张图;
例题
题面
思路
我们可以先构建出最小生成树;
然后枚举边,如果这条边加进去会形成环;
那么我们就用这条边替换最小生成树上最大的边;
如果这条边与最大的边相等,那么就替换次大的边;
试完所有连接会形成环的边以后,我们就得到了次小生成树;
d i s t ( i , j ) 表 示 点 i 往 上 2 j dist(i,j)表示点i往上2^j dist(i,j)表示点i往上2j所得到的最大边权
d i s t 2 ( i , j ) 表 示 点 i 往 上 2 j dist2(i,j)表示点i往上2^j dist2(i,j)表示点i往上2j所得到的次大边权
使用LCA来询问这个环上除了当前边的所有边权;
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int N = 1e5+10,M = 3e5+10;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int n,m,cnt,head[N],p[N];
//最大值 次大值
int depth[N],fa[N][20],dist[N][20],dist2[N][20];
struct Edge{
int u,v,val;
bool used;
}e[M];
struct Node{
int to,next,val;
}edge[M];
void add(int u,int v,int w){
++cnt;
edge[cnt].val = w;
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
int find(int x){
if(x == p[x]) return x;
return p[x] = find(p[x]);
}
ll kruskal(){
for(int i=1;i<=n;++i) p[i] = i;
sort(e+1,e+1+m,[](Edge p,Edge q)->bool
{
return p.val < q.val;
});
ll ret = 0;
for(int i=1,u,v,fu,fv;i<=m;++i){
u = e[i].u;
v = e[i].v;
fu = find(u);
fv = find(v);
if(fu != fv){
p[fu] = fv;
ret += e[i].val;
e[i].used = 1;
}
}
return ret;
}
void build(){
for(int i=1,u,v,w;i<=m;++i){
if(e[i].used){
u = e[i].u;
v = e[i].v;
w = e[i].val;
add(u,v,w);
add(v,u,w);
}
}
}
//初始化LCA
void bfs(int root){
memset(depth,0x3f,sizeof depth);
depth[0] = 0;
depth[root] = 1;
queue<int> q;
q.push(root);
while(!q.empty()){
int u = q.front();
q.pop();
for(int i=head[u];i;i=edge[i].next){
int to = edge[i].to;
if(depth[to] > depth[u] + 1){
depth[to] = depth[u] + 1;
q.push(to);
fa[to][0] = u;
dist[to][0] = edge[i].val;
dist2[to][0] = -INF;
for(int k=1;k<=19;++k){
int anc = fa[to][k-1];
fa[to][k] = fa[anc][k-1];
dist[to][k] = dist2[to][k] = -INF;
//从两个最大值、两个次大值转移
int distant[4] = {
dist[to][k-1],dist[anc][k-1],
dist2[to][k-1],dist2[anc][k-1]
};
for(int d=0;d<4;++d){
int dis = distant[d];
if(dis > dist[to][k]){
//最大值降为次大值
dist2[to][k] = dist[to][k];
//新的最大值
dist[to][k] = dis;
}//dis严格比最大值小
else if(dis!=dist[to][k]&&dis>dist2[to][k]){
dist2[to][k] = dis;
}
}
}
}
}
}
}
//这里查询的不是祖先
//而是替换掉能替换的最大的一条边
vector<int> disv;//存储每条链的距离
int LCA(int u,int v,int w){
disv.clear();
if(depth[u] < depth[v]){
swap(u,v);
}
//跳到同一层
for(int k=19;k>=0;--k){
if(depth[fa[u][k]] >= depth[v]){
disv.push_back(dist[u][k]);
disv.push_back(dist2[u][k]);
u = fa[u][k];
}
}
//一起往上跳
if(u!=v){
for(int k=19;k>=0;--k){
if(fa[u][k]!=fa[v][k]){
disv.push_back(dist[u][k]);
disv.push_back(dist2[u][k]);
disv.push_back(dist[v][k]);
disv.push_back(dist2[v][k]);
u = fa[u][k];
v = fa[v][k];
}
}
disv.push_back(dist[u][0]);
disv.push_back(dist2[u][0]);
disv.push_back(dist[v][0]);
disv.push_back(dist2[v][0]);
}
//环上的最大边和次大边
int dis1 = -INF,dis2 = -INF;
//处理除了u,v这条边以外,环上的所有边
for(auto d : disv){
if(d > dis1){
dis2 = dis1;
dis1 = d;
}//如果d和dis1相等了,那dis1和dis2就相等了
//那么我们构造次小生成树就失败了
else if(dis1 != d && d > dis2){
dis2 = d;
}
}
//接上u,v这条边,替换掉最大边
if(w > dis1){
return w - dis1;
}
//替换掉次大边
return w - dis2;
//如果存在dis1 == dis2 那么返回-INF即可
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v,w;i<=m;++i){
scanf("%d%d%d",&u,&v,&w);
e[i] = {u,v,w,false};
}
//最小生成树
ll mst = kruskal();
build();
bfs(1);//随便取个点当root
ll sec_mst = 1e18;
for(int i=1,u,v,w;i<=m;++i){
//看看连各个边的情况
if(e[i].used == false){
u = e[i].u,v = e[i].v,w = e[i].val;
sec_mst = min(sec_mst,mst + LCA(u,v,w));
}
}
printf("%lld\n",sec_mst);
return 0;
}