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(1≤i≤m) 条边,那么最小生成树的权值总和最小是多少。
输入格式
第一行有两个整数 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 1≤n≤2×105,n−1≤m≤2×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 1≤ui,vi≤n,ui=vi,1≤wi≤109
样例 #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;
}