Loading

「学习笔记」严格次小生成树算法(倍增)

看到标题,你可能会有疑问。次小生成树?那是个什么东东?我只会求最小生成树
我只能说,会求最小生成树就够了,最小生成树如果不会可以先去补好基础再来(bye~bye)
次小分为严格次小和非严格次小(应该是这么叫),严格次小一定会比最小的要小,说白了就是第二小,非严格次小可能会等于最小的
一般都是严格次小
OK,进入正题
严格次小生成树其实就与最小生成树差了一条边,否则就是你最小生成树求错了 去补基础
所以,我们先求出最小生成树,prim 或 kruskal,看情况而定
接下来就是加边了,我们已经生成了最小生成树,如果加任意一条边,都会构成环,所以我们在加边的同时,要删去一条边。
那么,问题来了,我们要删哪一条边呢?
我们想一下,想让他严格次小,是不是删去原树中最大的边,再加上新边就可以了?
为什么呢?
我们任意一条新边,都是大于等于最小生成树中最大的边的,如果不是,你最小生成树求错了,去补基础。
要想让我们的答案做到第二小,是不是我们要让它的增值最小。
增值怎么求?你新加的边的权值-你删去边的权值
已知新加的边比任意一条原边大于或等于,所以,替换最大边即可
我们每加入一条边,都要连接两个节点,我们找这两个结点的 lca,求出这两个节点到 lca 的各自的最大值,再对这两个最大值取一个 \(\max\),就是我们要找的最大值
但是!这里要注意,如果新加的边与最大边的权值相等,增值为 \(0\),那就无法达到严格次小这个条件,这时该怎么办?
我们可以再找次大边,不能让增值为 \(0\),所以还要记录次大边,我们在找最大值时判断一下,如果与新加的边权值相等,那么就返回次大边。
好了,现在道理解释完了,回归最根本的问题,怎么找最大边和次大边?
主角登场——倍增
利用倍增,可以求出最大边和次大边,具体怎么求,看代码吧,有简洁的注释。

#include <iostream>
#include <cstdio>
#include <algorithm>
typedef long long ll;
using namespace std;
const ll M = 3e5 + 5;
const ll N = 1e5 + 5;
const ll inf = 1e15 + 10;
ll n, m, sum, cnt;
ll lg[N], deep[N], st[N][21];
ll f[N], mx[N][21], me[N][21], h[N];
// mx 记录最大边 me 记录次大边 h 遍历最小生成树时要用 
bool vis[M];//记录第i条边是否用过 
struct edge//存边 
{
	ll u, v, w, nxt;
	bool operator < (const edge &b) const//重载运算符 
	{
		return w < b.w;
	}
} e[M], g[M];
//e 记录所有读入的边 g 记录构成最小生成树的边 
inline ll read()//快读 
{
	ll x = 0;
	bool flag = false;
	char ch = getchar();
	while(ch < '0' || ch > '9')
	{
		if(ch == '-')	flag = true;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9')
	{
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return flag ? ~x + 1 : x;
}
void add(ll u, ll v, ll w)//加边,建最小生成树 
{
	g[++cnt].u = u;
	g[cnt].v = v;
	g[cnt].w = w;
	g[cnt].nxt = h[u];
	h[u] = cnt;
}
ll find(ll x)//并查集查找 
{
	return f[x] == x ? f[x] : f[x] = find(f[x]);
}
void kruskal()//kruskal最小生成树算法 
{
	sort(e + 1, e + m + 1);
	ll tot = 0;
	for(ll i = 1; i <= m; ++i)
	{
		ll x = e[i].u, y = e[i].v, w = e[i].w, fx = find(x), fy = find(y);
		if(fx != fy)
		{
			f[fy] = fx;
			tot++;
			sum += w;
			vis[i] = true;
			add(x, y, w);
			add(y, x, w);
		}
		if(tot == n-1)	break;
	}
}
void dfs(ll u, ll fat)//深搜,找最大边和次大边 
{
	deep[u] = deep[fat]+1;
	st[u][0] = fat;
	for(ll i = 1; i <= lg[deep[u]]; ++i)
	{
		st[u][i] = st[st[u][i-1]][i-1];//更新st表 
		ll a = mx[u][i-1], b = mx[st[u][i-1]][i-1];//最大边 
		ll c = me[u][i-1], d = me[st[u][i-1]][i-1];//次大边 
		mx[u][i] = max(a, b);//找最大边 
		if(a == b)	me[u][i] = max(c,d);//找次大边 
		if(a > b)	me[u][i] = max(b,c);
		if(a < b)	me[u][i] = max(a,d);
	}
	for(ll i = h[u]; i; i = g[i].nxt)
	{
		ll v = g[i].v, w = g[i].w;
		if(v != fat)
		{
			mx[v][0] = w;//记录最大边,该节点到父节点的最大边就是这条连边 
			dfs(v, u);
		}
	}
}
ll LCA(ll x, ll y)//倍增求LCA 
{
	if(deep[x] < deep[y])
	{
		x = x ^ y;
		y = x ^ y;
		x = x ^ y;
	}
	while(deep[x] > deep[y])
	{
		x = st[x][lg[deep[x]-deep[y]]];
	}
	if(x == y)    return x;
	for(ll i = lg[deep[x]]; i>=0; --i)
	{
		if(st[x][i] != st[y][i])
		{
			x = st[x][i];
			y = st[y][i];
		}
	}
	return st[x][0];
}
ll fid(ll x, ll lca, ll w)
{
	ll answer = 0;
	for(ll i = lg[deep[x]]; i >= 0; --i)
	{
		if(deep[st[x][i]] >= deep[lca])
		{
			if(mx[x][i] == w)	answer = max(answer, me[x][i]);
			//因为严格次小,所以最大值相等,找次大值 
			else	answer = max(answer, mx[x][i]);//找最大值 
			x = st[x][i];
		}
	}
	return answer;
}
void work()
{
	ll ans = inf;
	for(ll i = 1; i <= m; ++i)//枚举每一条边,找到替换后结果最优的边 
	{
		ll x = e[i].u, y = e[i].v, w = e[i].w;
		if(vis[i] || x == y)	continue;
		//前面求最小生成树已经用过的边不算,自环不算 
		ll lca = LCA(x, y);//求lca 
		ll lmx = fid(x, lca, w),rmx = fid(y, lca, w);
		//找x到lca的最大值,找y到lca的最大值 
		if(max(lmx, rmx) != w)    ans = min(ans, sum + w - max(lmx, rmx));
		//只要最大值不等于这条边,计算次小生成树
		//为什么不能等于? 
		//因为是严格次小,这一加一减一个相等的数有什么变化?不符合严格次小
		//因为严格次小,所以要找一个最小的值 
	}
	printf("%lld\n",ans);//输出 
}
int main()
{
	n = read(), m = read();
	for(ll i = 1; i <= n; ++i)    f[i] = i;//并查集操作,更新每个点的父节点 
	for(ll i = 1; i <= m; ++i)
	{
		e[i].u = read(),e[i].v = read(),e[i].w = read();//读入边 
	}
	lg[0] = -1;
	for(ll i = 1; i <= n; ++i)
	{
		lg[i] = lg[i >> 1] + 1;//处理log 
	}
	kruskal();//先构造最小生成树 
	dfs(1, 0);//深搜 
	work();//求次小生成树 
	return 0;
}

可以去看看这道模板题严格次小生成树
喜欢的话可以鼓励一下,写的不好,勿喷 QWQ!

posted @ 2022-07-12 11:10  yi_fan0305  阅读(90)  评论(0编辑  收藏  举报