次小生成树

题目模型

给定一张 N N N 个点 M M M 条边的无向图,求无向图的(严格)次小生成树。

设最小生成树的边权之和为 s u m sum sum,严格次小生成树就是指边权之和大于 s u m sum sum 的生成树中最小的一个。


求解思路:

找一个不在最小生成树的边加进去,肯定会构成一个环,然后替换掉环中除了该边权值最大的一个边,便得到一个新的生成树。取所有这样的生成树中的最小值便是次小生成树。

为了保证次小生成树是严格次小,要保证替换掉的最大边权和加进去的边权不相等,如果相等的话,就替换掉次大边权。
如果不用保证严格次小,那么直接用最大边权替换就可以。

实现思路:

朴素做法:
对于每个节点,遍历得到该节点到最小生成树中其他节点的路径中权值最大和次大的边权。如果加进去 x 和 y 之间的一条边,那么就替换掉最小生成树中从 x 到 y 的路径中的最大边权或者次大边权。
每个点都遍历其余所有点,时间复杂度为 O ( n 2 + m ) O(n^2 + m) O(n2+m)

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 2010, mod = 1e9+7;
int T, n, m;
struct node{
	int x, y, w;
}a[N*N];
int pre[N], f[N];
int dist1[N][N], dist2[N][N];
vector<PII> e[N];

bool cmp(node a, node b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

void dfs(int st, int x, int fa)
{
	for(auto it:e[x])
	{
		int tx = it.fi, w = it.se;
		if(tx == fa) continue;
		
		dist1[st][tx] = dist1[st][x]; //继承最大值
		dist2[st][tx] = dist2[st][x];
		
		if(w > dist1[st][tx]) dist2[st][tx] = dist1[st][tx], dist1[st][tx] = w;
		else if(w < dist1[st][x] && w > dist2[st][tx]) dist2[st][tx] = w; //严格次小 
		
		dfs(st, tx, x);
	}
}

signed main(){
	Ios;
	cin>>n>>m;
	
	for(int i=1;i<=m;i++)
	{
		cin>>a[i].x>>a[i].y>>a[i].w;
	}
	sort(a+1, a+m+1, cmp);
	
	for(int i=1;i<=n;i++) pre[i] = i;
	
	int sum = 0;
	for(int i=1;i<=m;i++)
	{
		int x = a[i].x, y = a[i].y, w = a[i].w;
		if(find(x) != find(y))
		{
			pre[find(x)] = find(y);
			sum += w;
			f[i] = 1;
			
			e[x].pb({y, w}), e[y].pb({x, w});
		}
	}
	
	for(int i=1;i<=n;i++) dfs(i, i, 0);
	
	int ans = 1e18;
	for(int i=1;i<=m;i++)
	{
		if(f[i]) continue;
		
		int x = a[i].x, y = a[i].y, w = a[i].w;
		
		if(dist1[x][y] && w > dist1[x][y]) ans = min(ans, sum - dist1[x][y] + w);
		else if(dist2[x][y] && w > dist2[x][y]) ans = min(ans, sum - dist2[x][y] + w);
	}
	cout << ans;
	
	return 0;
}

LCA优化做法
找到环中除了加进去的边之外最大和次大的边权,也就是找到节点 x x x 和节点 y y y 到其最近公共祖先的路径中最大和次大的边权。
仿照求 LCA 时的倍增做法,维护从节点 i i i 2 j 2^j 2j 步的路径中的最大边权 d1[i, j] 和次大边权 d2[i, j]
更新方式也和 fa[i, j] 一样,取 d1[tx, j-1], d1[f[tx, j-1]][j-1], d2[tx, j-1], d2[f[tx, j-1][j-1] 中的最大值和次大值来更新。

那么在求 LCA 的过程中,就能得到整个路径中的最大边权和次大边权,用加进来的边权将其替换掉得到次小生成树。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long

const int N = 300010, mod = 1e9+7;
int T, n, m;
int a[N];
struct edgs{
	int x, y, w;
	bool used;
}edg[N];
int sum, pre[N];
vector<PII> e[N];
int dep[N], f[N][30], k;
int d[N];
int d1[N][30], d2[N][30]; //d1[i, j]:从i点往上跳2^j步,途经的最大边权。

bool cmp(edgs a, edgs b){
	return a.w < b.w;
}

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

void kruskal()
{
	for(int i=1;i<=n;i++) pre[i] = i;
	sort(edg+1, edg+m+1, cmp);
	
	for(int i=1;i<=m;i++)
	{
		int x = edg[i].x, y = edg[i].y, w = edg[i].w;
		int fx = find(x), fy = find(y);
		if(fx != fy){
			pre[fx] = fy;
			sum += w;
			edg[i].used = 1;
			
			e[x].push_back({y, w});
			e[y].push_back({x, w});
		}
	}
}

void bfs()
{
	dep[0] = 0, dep[1] = 1;
	queue<int> que;
	que.push(1);
	
	while(que.size())
	{
		int x = que.front(); que.pop();
		
		for(auto it : e[x])
		{
			int tx = it.fi, dis = it.se;
			if(tx == f[x][0]) continue;
			
			dep[tx] = dep[x] + 1;
			que.push(tx);
			
			f[tx][0] = x;
			d1[tx][0] = dis, d2[tx][0] = -1e9;
			for(int i=1;i<=k;i++)
			{
				int anc = f[tx][i-1];
				f[tx][i] = f[anc][i-1];
				d[0] = d1[tx][i-1], d[1] = d1[anc][i-1], d[2] = d2[tx][i-1], d[3] = d2[anc][i-1];
				for(int j=0;j<4;j++) //维护最大值和次大值 
				{
					if(d[j] > d1[tx][i]) d2[tx][i] = d1[tx][i], d1[tx][i] = d[j];
					else if(d[j] != d1[tx][i] && d[j] > d2[tx][i]) d2[tx][i] = d[j];
				}
			}
		}
 	}
}

int lca(int x, int y, int w)
{
	if(dep[x] < dep[y]) swap(x, y);
	
	int idx = 0;
	for(int i=k;i>=0;i--){
		if(dep[f[x][i]] >= dep[y]){
			d[++idx] = d1[x][i];
			d[++idx] = d2[x][i];
			x = f[x][i];
		}
	}
	
	for(int i=k;i>=0;i--)
	{
		if(f[x][i] != f[y][i])
		{
			d[++idx] = d1[x][i];
			d[++idx] = d2[x][i];
			d[++idx] = d1[y][i];
			d[++idx] = d2[y][i];
			x = f[x][i], y = f[y][i];
		}
	}
	d[++idx] = d1[x][0], d[++idx] = d1[y][0];
	
	int dist1 = -1e9, dist2 = -1e9; //找两个点到最近公共祖先路径中的最大和次大边权 
	for(int i=1;i<=idx;i++)
	{
		if(d[i] > dist1) dist2 = dist1, dist1 = d[i];
		else if(d[i] != dist1 && d[i] > dist2) dist2 = d[i];
	}
	
	if(w != dist1) return w - dist1; //加进来的边权-最大边权 
	return w - dist2; //最大边权相等那就次大边权 
}

signed main(){
	Ios;
	cin >> n >> m;
	
	k = log(n)/log(2);
	
	for(int i=1;i<=m;i++){
		int x,y,z;cin >> x>>y>>z;
		edg[i] = {x, y, z};
	}
	
	kruskal();
	
	bfs();
	
	int ans = 1e18;
	for(int i=1;i<=m;i++)
	{
		int x = edg[i].x, y = edg[i].y, w = edg[i].w;
		if(edg[i].used) continue;
		if(x != y) ans = min(ans, sum + lca(x, y, w)); //注意判掉自环
	}
	cout << ans;
	
	return 0;
}
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值