CF609E Minimum spanning tree for each edge 题解

2024/09/06

拖到今天才写,主要是开学了

CF609E Minimum spanning tree for each edge

题面:

题面翻译
题目描述

给你 n n n 个点, m m m 条边,如果对于一个最小生成树中要求必须包括第 i ( 1 ≤ i ≤ m ) i(1 \le i \le m) i(1im) 条边,那么最小生成树的权值总和最小是多少。

输入格式

第一行有两个整数 n n n m m m

接下来 m m m 行,每行有三个整数 u u u v v v w w w 表示一条权值为 w w w 的边连接 u u u v v v

输出格式

总共 m m m 行,第 i i i 行一个整数代表包括第 i i i 条边时的最小权值和。

说明/提示

1 ≤ n ≤ 2 × 1 0 5 , n − 1 ≤ m ≤ 2 × 1 0 5 1 \le n \le 2 \times 10^5,n-1 \le m\le 2 \times 10^5 1n2×105,n1m2×105

1 ≤ u i , v i ≤ n , u i ≠ v i , 1 ≤ w i ≤ 1 0 9 1 \le u_i,v_i \le n,u_i \neq v_i,1 \le w_i \le 10^9 1ui,vin,ui=vi,1wi109

样例 #1
样例输入 #1
5 7
1 2 3
1 3 1
1 4 5
2 3 2
2 5 3
3 4 2
4 5 4
样例输出 #1
9
8
11
8
8
8
9

这道题注意考察经典生成树结论: 次小生成树只会从原最小生成树替换掉一条边,替换掉两条边肯定不优!

一个简短的证明:

不难注意到,用 Kruskal 算法求最小生成树时,我们 放弃掉两条最小生成树边,用其他两条权值去替换只放弃掉一条边,用其他一条边替换 ,两种决策显然第二种更优!

那么我们强制使用一条边,先看他是不是在最小生成树上?如果是,直接输出最小生成树权值。

如果不是,我们看如何替换。

当我们求出最小生成树后,再添加一条边,必然出现环,此时就是基环树

我们都知道,只能在环上断边才能重新变成树,所以问题变成了环上断边。

因为我们要让最小生成树权值最小,我们肯定断环上权值最大的边,考虑树剖。

我们用树剖求出强制边 ( u , v ) (u,v) (u,v) 所在环上最大值:因为 ( u , v ) (u,v) (u,v) 强制,实际上是 p a t h : ( u , v ) path : (u,v) path:(u,v) 上的边权最大值。

所以 sol 就是 Kruskal + 树剖(边权转点权)

AC-code:

#include<bits/stdc++.h>
using namespace std;
#define int long long	
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
const int M = 2e5+5,N = M,inf = 0x3f3f3f3f3f3f3f3fLL;
int n,m,ans,det[M],s[N];
array<int,4> e[M];
bool vis[N];
int head[N],nxt[N<<1],to[N<<1],cnt,val[N<<1];
void init() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v,int w) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	val[cnt] = w;
	head[u] = cnt++;
}
int find(int x) {
	if(s[x] ^ x) s[x] = find(s[x]);
	return s[x];
}

void kruskal() {
	for(int i = 1;i<=n;i++) s[i] = i;
	int tot = 0;
	for(int i = 1;i<=m;i++) {
		int fx = find(e[i][1]),fy = find(e[i][2]);
		if(fx ^ fy) {
			s[fx] = fy;
			vis[e[i][3]] = true;
			add(e[i][1],e[i][2],e[i][0]);
			add(e[i][2],e[i][1],e[i][0]);
			ans += e[i][0];
			tot++;
			if(tot == n - 1) break;
		}
	}
}

int dep[N],fa[N],siz[N],son[N],id[N],w[N],_w[N],top[N],num;

void dfs1(int x,int f) {
	fa[x] = f;
	siz[x] = 1;
	dep[x] = dep[f] + 1;
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ f) {
			dfs1(y,x);
			w[y] = val[i];
			siz[x] += siz[y];
			if(siz[son[x]] < siz[y]) son[x] = y;
		}
	}
}

void dfs2(int x,int topx) {
	top[x] = topx;
	id[x] = ++num;
	_w[num] = w[x];
	if(!son[x]) return;
	dfs2(son[x],topx);
	for(int i = head[x];~i;i = nxt[i]) {
		int y = to[i];
		if(y ^ fa[x] && y ^ son[x]) dfs2(y,y);
	}
}

namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)

int mx[N<<2];

void push_up(int p) {
	mx[p] = max(mx[ls],mx[rs]);
}

void build(int p,int pl,int pr) {
	if(pl == pr) {
		mx[p] = _w[pl];
		return;
	}
	build(ls,pl,mid);
	build(rs,mid+1,pr);
	push_up(p);
}

int query(int p,int pl,int pr,int l,int r) {
	if(l > r) return 0;
	if(l <= pl && pr <= r) return mx[p];
	if(r <= mid) return query(ls,pl,mid,l,r);
	else if(l > mid) return query(rs,mid+1,pr,l,r);
	else return max(query(ls,pl,mid,l,r),query(rs,mid+1,pr,l,r));
}

}

int query(int x,int y) {
	int re = 0;
	while(top[x] ^ top[y]) {
		if(dep[top[x]] < dep[top[y]]) swap(x,y);
		re = max(re,sgt::query(1,1,n,id[top[x]],id[x]));
		x = fa[top[x]];
	}
	if(dep[x] > dep[y]) swap(x,y);
	re = max(re,sgt::query(1,1,n,id[x] + 1,id[y]));
	return re;
}
int a[N];

signed main() {
	init();
	n = rd(),m = rd();
	for(int i = 1;i<=m;i++) 
		e[i][1] = rd(),e[i][2] = rd(),e[i][0] = rd(),e[i][3] = i;
	sort(e + 1,e + m + 1);
	kruskal();
	dfs1(1,0),dfs2(1,1);
	sgt::build(1,1,n);
	for(int i = 1;i<=m;i++) {
		if(vis[e[i][3]]) a[e[i][3]] = ans;
		else  {
			int re = query(e[i][1],e[i][2]);
			a[e[i][3]] = ans + e[i][0] - re; 
		}
	}
	for(int i = 1;i<=m;i++) wt(a[i]),putchar('\n');
	
	return 0;
}
  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值