QOJ14025 Bot Friends 2

答案形如给定一个带点权的完全图,两点间的距离为给定图中它们的最短路,定义其内向生成树的代价为边权加每个点的点权乘上其子节点数量(入度)的和,求解代价最小的内向生成树,其中前者等价于原题的移动操作,后者等价于合并操作,记每个点的点权为 \(a_i\),给定图中 \(i\)\(j\) 的最短路为 \(\text{dis}(i,j)\),考虑 \(a_i=0\) 的情况,此时答案为原图的最小生成树,考虑 \(a_i > 0\) 的情况,难以刻画给每条边定向的贡献,考虑更改生成树的边权以及代价,定义边权为 \(a_i+\text{dis}(i,j)+a_j\) 且代价为边权和,可以发现将这样求出的生成树的代价减去 \(\sum\limits_{i=1}^n a_i\) 加上根的点权等于原来的代价,贪心地选取根为 \(a_i\) 最小的点,那么可以在给定图的基础上新建 \(n\) 个虚点并连接 \((i,i+n,a_i)\) 得到图 \(G\),考虑快速求解边权为最短路的最小生成树,记 \(c_i\) 表示与 \(i\) 距离最近的虚点,\(d_i\) 表示 \(i\)\(c_i\) 的最短路,可以发现只需要求解边集 \((c_u,c_v,d_u+d_v+w)\) 构成的最小生成树,其中 \((u,v,w) \in G\),根据 \(\text{Kruskal}\) 的贪心理论可以证明,则时间复杂度为 \(\text{O}(n \log n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n,m,u,v,c,fa[500001],vis[1000001],col[1000001];
ll s,w,a[500001],dis[1000001];
vector <pair<int,ll>> G[1000001];
priority_queue <pair<ll,int>,vector<pair<ll,int>>,greater<pair<ll,int>>> q;
vector <tuple<ll,int,int>> e;
int find(int x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m),s=0,w=1e18,e.clear();
		for(int i=1;i<=n*2;i++) vis[i]=0,dis[i]=1e18,G[i].clear();
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]),fa[i]=i,G[i].push_back({i+n,a[i]}),G[i+n].push_back({i,a[i]});
		while(m--) scanf("%d%d%d",&u,&v,&c),G[u].push_back({v,c}),G[v].push_back({u,c});
		for(int i=1;i<=n;i++) dis[i+n]=0,col[i+n]=i,q.push({0,i+n});
		while(q.size()){
			auto [v,x]=q.top();
			q.pop();
			if(vis[x]) continue;
			vis[x]=1;
			for(auto [y,w]:G[x]) if(dis[y]>dis[x]+w) dis[y]=dis[x]+w,col[y]=col[x],q.push({dis[y],y});
		}
		for(int i=1;i<=n;i++) for(auto [j,k]:G[i]) e.push_back({dis[i]+dis[j]+k,col[i],col[j]});
		sort(begin(e),end(e));
		for(auto [w,u,v]:e) if(find(u)!=find(v)) fa[find(u)]=find(v),s+=w;
		for(int i=1;i<=n;i++) s-=a[i],w=min(w,a[i]);
		printf("%lld\n",s+w);
	}
	return 0;
}
posted @ 2025-09-22 21:05  zyxawa  阅读(38)  评论(0)    收藏  举报