The 3rd Universal Cup Semifinals L 个人题解

link

非常好图题。

鉴于暂时没有官方题解,本文的部分结论可能存在问题,若能指正将感激不尽。

第一步转化

我们建立一张有向完全图,定义一条有向边 \((u, v)\) 的权值为两点在原图上的最短路加上 \(a_v\),其意义是 bot \(u\)\(v\) 节点参与合并。

于是我们只需要求这个图的最小定根内向生成树,需要做 \(n\) 遍 DMST。

这显然是不能接受的,我们需要分析性质。

第二步转化

考虑能不能变成无向图 MST,发现居然是能的。

具体的,我们建立一张无向完全图,定义一条无向边 \((u,v)\) 的权值为两点在原图上的最短路加上 \(a_u+a_v\),求出来 MST 后,我们需要减去所有的 \(a_i\),把每个 bot 的来源点的 \(a\) 去掉。

这个时候,我们需要定根,不难发现直接定在 \(a\) 最小的点即可。

这个做法的正确性在于任意一颗生成树都需要经过上面的删来源和定根的操作,所以偏移值是相同的。

于是只需要跑 MST,简化了很多,但还是不足以通过。

划分等效块

不难通过最短路算法求出每个点的最优合并点,这里最优合并点定义为最短路加上这个点的 \(a\) 最小。

发现整个图会划分为若干个连通块,每个连通块内的最优合并点相同。可以用反证法证明,即考虑存在一些点把某个连通块切开,与最短路的前提矛盾。

我们不难证明对于最优合并点不是自己的点,在上述的无向图中这个边一定是优先合并的,换句话说,我们可以把(最优合并点不是自己)的点先并到最优合并点上。

相当于我们先求了一些连通块的生成树,这些生成树都是菊花图,再加一些边把这片菊花森林连通。

那么怎么找这些菊花根之间的距离关系呢?

最后一步

我一开始想的是用 Boruvka 做,但没实现出来。

回顾图的形态,所有最优合并点相同的点形成一个连通块,也就是感性上只有相邻连通块之间的边会参与构成 MST。

证明大概考虑反证,即如果 MST 中某条边是某个连通块的根跨过若干连通块,连向一个和这个连通块不相邻的连通块的根,那么可以把这条路径拆开,拆散到途径的连通块内。

严谨证明等官方题解吧,作者实力有限 qwq。

于是只需要加入相邻连通块的根之间的边,跑 MST 即可。

复杂度为 \(O(n+m\log n)\)

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
	x=0;char c=getchar();bool f=0;
	for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
	for(;c>='0'&&c<='9';c=getchar())
	x=(x<<1)+(x<<3)+(c^48);
	x=(f?-x:x);
}
const int N=1e6+5;
int T, n, m;
ll a[N], dis[N]; int fr[N];
vector<pii> e[N];
bool v[N];
struct node{
	int x, y; ll z;
};
vector<node> vec;
int fa[N];
int get(int x){
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
bool merge(int x, int y){
	x=get(x); y=get(y);
	if(x==y) return false;
	fa[x]=y; return true;
}
void solve(){
	read(n); read(m);
	priority_queue<pair<ll, int> > pq;
	for(int i=1; i<=n; ++i) {
		read(dis[i]); fr[i]=i; fa[i]=i; v[i]=0;
		a[i]=dis[i];
		e[i].clear();
		pq.push(mapa(-dis[i], i));
	}
	for(int i=1, x, y, z; i<=m; ++i){
		read(x); read(y); read(z);
		e[x].ep(y, z); e[y].ep(x, z);
	}
	while(!pq.empty()){
		int x=pq.top().se; pq.pop();
		if(v[x]) continue;
		v[x]=1;
		for(auto edg:e[x]){
			int y=edg.fi, z=edg.se;
			if(dis[y]>dis[x]+z){
				fr[y]=fr[x];
				dis[y]=dis[x]+z;
				pq.push(mapa(-dis[y], y));
			}
		}
	}
	ll ans=0;
	for(int i=1; i<=n; ++i){
		if(fr[i]!=i){
			ans+=dis[i]+a[i];
			assert(merge(i, fr[i]));
		}
	}
	vec.clear();
	for(int x=1; x<=n; ++x){
		for(auto edg:e[x]) {
			int y=edg.fi;
			if(x>y) continue;
			if(fr[x]!=fr[y]){
				vec.ep((node){fr[x], fr[y], dis[x]+dis[y]+edg.se});
			}
		}
	}
	sort(vec.begin(), vec.end(), [&](node x, node y){return x.z<y.z;});
	for(auto t:vec){
		if(merge(t.x, t.y)){
			ans+=t.z;
		}
	}
	a[0]=a[1];
	for(int i=1; i<=n; ++i){
		ans-=a[i];
		a[0]=min(a[0], a[i]);
	}
	ans+=a[0];
	printf("%lld\n", ans);
}
int main(){
	// freopen("D:\\nya\\acm\\B\\test.in","r",stdin);
	// freopen("D:\\nya\\acm\\B\\test.out","w",stdout);
	read(T);
	while(T--){
		solve();
	}
	return 0;
}
posted @ 2025-09-09 21:37  Displace  阅读(55)  评论(0)    收藏  举报