356. 次小生成树(LCA倍增算法,换边)

356. 次小生成树 - AcWing题库

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

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

输入格式

第一行包含两个整数 N 和 M。

接下来 M 行,每行包含三个整数 x,y,z,表示点 x 和点 y 之前存在一条边,边的权值为 z。

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

数据范围

N≤105,M≤3×105
1≤x,y≤N
0≤z≤106

输入样例:
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
输出样例:
11

解析: 

先求出任意一棵最小生成树,设边权之和为sum。我们称在这棵最小生成树中的N-1条边为“树边”,其他M-N+1条边为”非树边“。

把一条非树边(x,y,z)添加到最小生成树中,会与树上 x,y 之间的路径一起形成一个环。设树上 x,y 之间的路径上的最大边权为 val1 ,严格次打边权为 val2(val1>val2)。

若 z>val1,则把 val1 对应的那条边替换成 (x,y,z)这条边,就得到了严格次小生成树的一个候选答案,边权之和为 sum-val1+z。

若 z=val1,则把 val2 对应的那条边替换成 (x,y,z)这条边,就得到了严格次小生成树的一个候选答案,边权之和为 sum-val2+z。

枚举每条非树边,添到最小生成树中,计算出上述所有”候选答案“。在候选答案中取最小值就得到了整张无向图的严格次小生成树。因此,我们要解决的主要问题是:如何快速求出一条路径上最大边权与严格次大边。

可以用树上倍增算法来进行预处理。设F[x,k]表示 x 的 2^k 辈祖先,G[x,k,0] 与 G[x,k,1] 分别表示从 x 到 F[x,k] 的路径上的最大边权和严格次大边权(最大边权不等于次大边权)。于是任意 k 属于[1,logN] 有:

F[x,k]=F[F[x,k-1]][k-1]

G[x,k,0]=max(G[x,k-1,0],G[F[x,k-1],k-1,0])

对于 G[x,k,1]:
G[x,k,1]=max(G[x,k-1,1],G[F[x,k-1],k-1,1]),G[x,k-1,0]=G[F[x,k-1],k-1,0]
G[x,k,1]=max(G[x,k-1,0],G[F[x,k-1],k-1,1]),G[x,k-1,0] G[x,k,1]=max(G[x,k-1,1],G[F[x,k-1],k-1,0]),G[x,k-1,0]>G[F[x,k-1],k-1,0]

当 k=0 时,有初值:

F[x,0]=fa(x)

G[x,0,0]=edge(x,fa(x))

G[x,0,1]=负无穷

时间复杂度为O(MlogN)

 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int N = 1e5 + 5, M = 3e5 + 5,INF=0x3f3f3f3f;
struct Edge {
	int a, b, c;
	bool used;
	bool operator< (const Edge& t)const {
		return c < t.c;
	}
}edge[M];
int n, m;
int p[N];
int h[N], e[M * 2], w[M * 2], ne[M * 2], idx;
int depth[N], d1[N][17], d2[N][17],fa[N][17];
int q[N];

int find(int a) {
	if (p[a] == a)return a;
	return p[a] = find(p[a]);
}

LL Kruskal() {
	sort(edge+1, edge+m + 1);
	for (int i = 1; i <= n; i++)p[i] = i;
	LL ret = 0;
	for (int i = 1; i <= m; i++) {
		int a = find(edge[i].a), b = find(edge[i].b);
		if (a != b) {
			p[a] = b;
			edge[i].used = 1;
			ret += edge[i].c;
		}
	}
	return ret;
}

void add(int a, int b, int c) {
	e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void build() {
	memset(h, -1, sizeof h);
	for (int i = 1; i <= m; i++) {
		if (edge[i].used) {
			add(edge[i].a, edge[i].b, edge[i].c);
			add(edge[i].b, edge[i].a, edge[i].c);
		}
	}
}

void bfs() {
	int hh = 0, tt = 0;
	memset(depth, 0x3f, sizeof depth);
	depth[0] = 0, depth[1] = 1;
	q[tt++] = 1;
	while (hh != tt) {
		int t = q[hh++];
		if (hh == N)hh = 0;
		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;
				if (tt == N)tt = 0;
				fa[j][0] = t;
				d1[j][0] = w[i];
				d2[j][0] = -INF;
				for (int k = 1; k< 17; k++) {
					int anc = fa[j][k - 1];
					fa[j][k] = fa[anc][k - 1];
					int dist[4] = { d1[j][k - 1],d2[j][k - 1],d1[anc][k - 1],d2[anc][k - 1] };
					d1[j][k] = d2[anc][k] = -INF;
					for (int u = 0; u < 4; u++) {
						int d = dist[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 lca(int a, int b, int c) {
	int dist[N * 2];
	int cnt = 0;
	if (depth[a] < depth[b])swap(a, b);
	for (int i = 16; i >= 0; i--) {
		if (depth[fa[a][i]] >= depth[b]) {
			dist[cnt++] = d1[a][i];
			dist[cnt++] = d2[a][i];
			a = fa[a][i];
		}
	}
	if (a != b) {
		for (int i = 16; i >= 0; i--) {
			if (fa[a][i]!=fa[b][i]) {
				dist[cnt++] = d1[a][i];
				dist[cnt++] = d2[a][i];
				dist[cnt++] = d1[b][i];
				dist[cnt++] = d2[b][i];
				a = fa[a][i];
				b = fa[b][i];
			}
		}
		dist[cnt++] = d1[a][0];
		dist[cnt++] = d1[b][0];
	}
	int dist1 = -INF, dist2 = -INF;
	for (int i = 0; i < cnt; i++) {
		int d = dist[i];
		if (d > dist1)dist2 = dist1, dist1 = d;
		else if (d != dist1 && d > dist2)dist2 = d;
	}
	if (c > dist1)return c - dist1;
	if (c > dist2)return c - dist2;
	return INF;
}

int main() {
	cin >> n >> m;
	for (int i = 1,a,b,c; i <= m; i++) {
		scanf("%d%d%d", &edge[i].a, &edge[i].b, &edge[i].c);
	}
	LL sum = Kruskal();
	build();
	bfs();
	LL ret = 1e18;
	for (int i = 1; i <= m; i++) {
		if (!edge[i].used) {
			ret = min(ret, sum + lca(edge[i].a, edge[i].b, edge[i].c));
		}
	}
	printf("%lld\n", ret);
	return 0;
}

你可能感兴趣的:(#,最近公共祖先,算法,图论)