CF2164E题解

传送门

我的博客

思路

首先肯定有一个基础花费为各边边权之和,将第二种操作想象为连向\(x\)\(z\)的边权为一条路径上编号最大边的边权的虚边.

考虑什么时候会用到第二种操作,根据欧拉回路的启发,若某个点的度数为奇数,一定需要且只需要一条第二种虚边,因此添加的虚边的边数为\(\frac{n}{2}\),其中\(n\)为奇点个数(定为偶数).故只需要最小化这些虚边的边权.

先引入kruskal重构树,考虑一条边能作为哪些奇点之间的虚边,满足边权编号最大就相当于按编号从小到大枚举每条边去拓展合并它能连奇点的联通块,这样它所在的联通块内的奇点都可以相互建立起以它为边权的虚边.遇到不断合并联通块这种东西常用的trick即为kruskal重构树,像[IOI 2018] werewolf 狼人,即考虑kruskal并查集合并的过程,将生成树上的边看做一个点连接它所连接的两个联通块,此题目中点权即为连接这两个联通块的边中边权的最小值.

那么考虑边权最小,容易发现它能连接的奇点在kruskal重构树上它的祖先们都能连,因为祖先的联通块包含它的联通块,于是遍历kruskal重构树,将每个点的点权和他祖先的点权取min即为此联通块中奇点中虚边边权的最小值,随便两两配对都是一样的,答案为\(\frac{奇点个数}{2}*val_u\),如遇到奇点个数为奇数的情况,则一定有一个落单的点,传到父亲让他和其他联通块中的点匹配即可.

code

代码应该还算比较好懂.

#include <bits/stdc++.h>
using namespace std;
const int M = 2e6+15;
int n,m,u[M],v[M],w[M],fa[M],val[M],in[M],tot,siz[M];
long long ans;
vector <int> e[M];
int find(int x){
	return x==fa[x]?x:fa[x]=find(fa[x]);
}
void dfs(int u,int mi){
	val[u] = min(val[u],mi);
	for(auto v : e[u]) dfs(v,val[u]) , siz[u]+=siz[v];
	ans += 1ll*siz[u]/2*val[u] , siz[u]%=2;
	return ;
}
int main(){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		tot = n , ans = 0;
		for(int i = 1 ; i<=n+m ; i++) fa[i] = i , val[i] = 1e9 , siz[i] = 0 , in[i] = 0 , e[i].clear();
		for(int i = 1 ; i<=m ; i++){
			scanf("%d%d%d",&u[i],&v[i],&w[i]);
			++in[u[i]] , ++in[v[i]] , ans+=w[i];
		}  
		for(int i = 1 ; i<=n ; i++) if(in[i]&1) siz[i] = 1;
		for(int i = 1 ; i<=m ; i++){
			int fx = find(u[i]) , fy = find(v[i]);
			if(fx!=fy) fa[fx] = fa[fy] = ++tot , e[tot].push_back(fx) , e[tot].push_back(fy);
			val[fa[fx]] = min(val[fa[fx]],w[i]);//注意点权需要在连接这两个联通块之间的边取min
		}
		dfs(tot,1e9);
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2025-11-11 16:14  lrj3247  阅读(1)  评论(0)    收藏  举报