求出最小生成树,并以“树边”进行倍增,用于维护lca和两点间路径中的某条最大边和次小边,然后暴力枚举非树边,即可获得严格次小生成树,详细见代码
ps:在寻找非严格次小生成树时,也可以利用树上倍增的想法,从而将非严格次小生成树的复杂度变为O(mlogn),只需要将代码中的维护次小边数组去掉即可
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 2147483647000000
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define p_queue priority_queue
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}
ll lcm(ll a, ll b) {
return a / gcd(a, b) * b;
}
ll n , m, cnt;
ll used[300005], head[300005]; //used表示哪些边已用
ll fa[300005],mst_value = 0; //mst_value为最小生成树的大小
ll vis[300005] , deep[300005]; //vis用于bfs,deep用于存深度用于计算lca
ll maxn[300005][25], minn[300005][25], bz[300005][25]; //maxn[i][j]表示以i为节点向上走2^j次方个节点,这条路径上的最大边
//minn同maxn即为次小边。
//bz[i][j]用于存放以i出发向上2^j的节点是谁用于求lca
struct e
{
ll u, t, w, next;
bool operator < (const e& no) const{
return w < no.w;
}
}edge[300005],b_edge[600005];
void add(ll f, ll t, ll w)
{
b_edge[cnt].w = w;
b_edge[cnt].t = t;
b_edge[cnt].next = head[f];
head[f] = cnt ++;
}
struct node
{
ll v , deep;
node(ll a, ll b):v(a),deep(b){}
};
void Init()
{
mem(head,-1);
cnt = 0;
for(int i = 1 ; i <= n ; i ++)
fa[i] = i;
}
ll findroot(ll x)
{
if(fa[x] == x)
return x;
return fa[x] = findroot(fa[x]);
}
bool merge(ll x ,ll y)
{
ll fax = findroot(x);
ll fay = findroot(y);
if(fax != fay)
{
fa[fax] = fay;
return true;
}
return false;
}
void kruskal()
{
ll cnt = 0;
for(ll i = 1 ; i <= m ; i ++)
{
if(cnt == n-1)
break;
ll v = edge[i].t , u = edge[i].u;
if(merge(v,u))
{
cnt++;
used[i] = 1; //标记用了哪些边
add(edge[i].u,edge[i].t,edge[i].w); //将树边加入前向星,非树边不管
add(edge[i].t,edge[i].u,edge[i].w);
mst_value += edge[i].w;
}
}
}
void bfs(ll root , ll cur) // 广搜初始化maxn,minn以及deep
{
minn[root][0] = - INF; //次小边数组初始化
deep[root] = 0; //根节点深度初始化
queue <node> q;
q.push(node(root,cur));
vis[root] = 1;
while(!q.empty())
{
node no = q.front();
q.pop();
for(ll i = head[no.v] ; i != -1 ; i = b_edge[i].next)
{
ll v = b_edge[i].t;
if(!vis[v])
{
vis[v] = 1;
deep[v] = deep[no.v] + 1;
bz[v][0] = no.v;
maxn[v][0] = b_edge[i].w; // 以v出发向上2^0个节点,这条路径中,只有一条路即为这条边
minn[v][0] = -INF;
q.push(node(v,deep[v]));
}
}
}
}
void Deal() //倍增处理
{
for(ll i = 1 ; i <= 20 ; i ++)
{
for(ll j = 1 ; j <= n ; j ++)
{
//以j出发向上2^j次方的节点 = 以j+2^(j-1)出发向上2^(j-1)
bz[j][i] = bz[bz[j][i-1]][i-1]; //处理bz数组,计算节点
maxn[j][i] = max(maxn[j][i-1], maxn[bz[j][i-1]][i-1]);
minn[j][i] = max(minn[j][i-1], minn[bz[j][i-1]][i-1]);
//如果两个区间不同,则将较小的那条边给次小数组维护
if(maxn[j][i-1] > maxn[bz[j][i-1]][i-1])
minn[j][i]=max(minn[j][i], maxn[bz[j][i-1]][i-1]);
else if(maxn[j][i-1] < maxn[bz[j][i-1]][i-1])
minn[j][i]=max(minn[j][i], maxn[j][i-1]);
}
}
}
ll LCA(ll x ,ll y) //倍增处理LCA
{
if(deep[x] < deep[y]) //先找出深度较深的点
swap(x,y);
for(int i = 20 ; i >= 0 ; i --) //将深度深的移动的和浅的相同
{
if(deep[bz[x][i]] >= deep[y])
x = bz[x][i];
}
if (x == y) return x; //如果重合,说明该点就是LCA
for(int i = 20 ; i >= 0 ; i --) //如果不重合,移动步数从大到小,如果移动后两个节点不一样,则更新
{
if(bz[x][i] ^ bz[y][i])
x = bz[x][i] ,y = bz[y][i];
}
return bz[x][0];
}
ll path_maxn(ll u , ll v, ll w)
{
ll ans = -INF;
for(int i = 20 ; i >= 0 ; i --)
{
if(deep[bz[u][i]] >= deep[v]) //与LCA类似,用于寻找路径中的边的最大值
{
if(w != maxn[u][i])//如果即将加入的非树边和这条路径中(树边)的最大值不相同
ans = max(ans,maxn[u][i]); //那么可以作为即将删除的边
else
ans = max(ans,minn[u][i]); //如果相同,则将这棵树中的次小值作为即将删除边
u = bz[u][i];
}
}
return ans;
}
int main(void)
{
scanf("%lld %lld",&n ,&m);
Init();
for(ll i = 1 ; i <= m ; i ++)
scanf("%lld %lld %lld", &edge[i].u, &edge[i].t, &edge[i].w);
sort(edge+1,edge+1+m);
kruskal();
bfs(1,0);
Deal();
ll ans = INF;
for(ll i = 1; i <= m ; i ++)
{
if(!used[i])
{
ll u = edge[i].u;
ll v = edge[i].t;
ll w = edge[i].w;
ll lca = LCA(u, v);
ll ans_ma = path_maxn(u, lca, w);
ll ans_mi = path_maxn(v, lca, w);
ans = min(ans, mst_value - max(ans_ma, ans_mi) + w);
}
}
printf("%lld\n",ans);
}