最小生成树
Basic
从一个图里抽取一些边使其构成一棵树,且总边权最小

Function 01 [Kruskal]
贪心算法
选取当前最小边权的边 
用并查集判断该边所连的点的连通性 

Question 01 [ACP2023 最短网络]
模板题

Code
#include<bits/stdc++.h>
using namespace std;
const int N=107,M=10056;
int n,Distance[N][N],cnt,father[N],ans;
struct edge{
	int ll,rr,w;
	bool operator <(const edge &rhs)const{
		return w<rhs.w;
	}
}k[M]; 
int find(int x){
	if(father[x]==x)return x;
	return father[x]=find(father[x]);
}
void merge(int x,int y){
	x=find(x),y=find(y);
	father[x]=y;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)father[i]=i;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&Distance[i][j]);
	for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)k[++cnt]={i,j,Distance[i][j]};
	sort(k+1,k+cnt+1);
	int shit=0;
	for(int i=1;i<=cnt;i++){
		if(shit==n-1)break;
		if(find(k[i].ll)==find(k[i].rr))continue;
		merge(k[i].ll,k[i].rr);
		ans+=k[i].w;
		++shit;
	}
	printf("%d",ans);
	return 0;
} 

Function 02 [Prim] 
每次选择到已有连通块距离最近的点加入连通块 
和 Dijstra 相似 

Question 02 [ACP2024 局域网]
同样是模板

Code 
#include<bits/stdc++.h>
using namespace std;
const int N=107,M=10009;
int n,k,tot,dis[N];
bool st[N]={};
struct edge{
	int to,len;
};
struct node{
	int id,len;
	bool operator <(const node &rhs)const{
		return len>rhs.len;
	}
};
vector<edge> line[N];
priority_queue<node> q;
void Prim(int start){
	dis[start]=0;
	q.push({start,0});
	while(!q.empty()){
		node top=q.top();
		q.pop();
		if(st[top.id])continue;
		//printf("%d\n",top.id);
		st[top.id]=true;
		tot-=top.len;
		for(auto i:line[top.id]){
			if(st[i.to])continue;
			//不能只在此处判断!
			//同一节点可能会多次被优化
			//而上一次入堆有可能还没有被标记
			//导致队里有重复元素
			//如取出时不判断就会WA 
			if(dis[i.to]>i.len){
				dis[i.to]=i.len;
				q.push({i.to,i.len});
			}
		}
	} 
}
int main(){
	memset(dis,0x3f,sizeof dis);
	int l,r,w;
	scanf("%d%d",&n,&k);
	for(int i=1;i<=k;i++){
		scanf("%d%d%d",&l,&r,&w);
		if(w==0)continue;
		line[l].push_back({r,w});
		line[r].push_back({l,w});
		tot+=w;
	}
	Prim(1);
	printf("%d",tot);
	return 0;
}

Question 03 [ACP2025 繁忙的都市]
最小生成树一定是瓶颈生成树 
仍然是模板
 
Code
#include<bits/stdc++.h>
using namespace std;
const int N=308,M=10098;
int n,m,Distance[N],big=-1;
bool st[N];
struct line{int to,len;};
struct node{
	int id,len;
	bool operator <(const node &rhs)const{
		return len>rhs.len;
	}
};
priority_queue<node> q;
vector<line> lable[N];
void Prim(int start){
	Distance[start]=0;
	q.push({start,0});
	while(!q.empty()){
		node top=q.top();
		q.pop();
		if(st[top.id])continue;
		st[top.id]=true;
		big=max(big,top.len);
		for(auto it:lable[top.id]){
			if(Distance[it.to]>it.len){
				Distance[it.to]=it.len;
				q.push({it.to,it.len});
			}
		}
	}
}
int main(){
	memset(Distance,0x3f3f3f,sizeof Distance);
	int l,r,w;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d%d",&l,&r,&w),lable[l].push_back({r,w}),lable[r].push_back({l,w});
	Prim(1);
	printf("%d %d",n-1,big);
	return 0;
} 

Question 04 [ACP2026 联络员] 
存在必选边的最小生成树
明显必须使用 Kruskal
必选边用并查集 Merge 并统计答案
可选边正常统计即可

Code
#include<bits/stdc++.h>
using namespace std;
const int N=2048,M=10047;
int n,m,father[N],cnt,ans;
int find(int x){
	if(x==father[x])return x;
	return father[x]=find(father[x]);
}
struct line{
	int l,r,len;
	bool operator <(const line &rhs)const{
		return len<rhs.len; 
	}
}k[M];
int main(){
	int op,l,r,len;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)father[i]=i;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&op,&l,&r,&len);
		if(op==1){
			ans+=len;
			father[find(l)]=find(r);
		}else{
			k[++cnt]={l,r,len};
		}
	} 
	sort(k+1,k+cnt+1);
	for(int i=1;i<=cnt;i++){
		if(find(k[i].l)==find(k[i].r))continue;
		father[find(k[i].l)]=find(k[i].r);
		ans+=k[i].len;
	}
	printf("%d",ans);
	return 0;
} 

Question 05 [连接格点] 
强行连边跑模板即可
坐标需要哈希映射

Code
#include<bits/stdc++.h>
using namespace std;
const int N=1023;
struct line{int l,r,len;}k[2*N*N];
int father[N*N],n,m,cnt,ans;
inline int hasher(int x,int y){
	return x*m-m+y;
}
int find(int x){
	if(x==father[x])return x;
	return father[x]=find(father[x]);
}
bool cmp(line a,line b){
	return a.len<b.len;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int a,b,c,d;
	cin>>n>>m;
	for(int i=1;i<=m*n;i++)father[i]=i;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		if(j<m)k[++cnt]={hasher(i,j),hasher(i,j+1),2};
		if(i<n)k[++cnt]={hasher(i,j),hasher(i+1,j),1};
	}
	while(cin>>a>>b>>c>>d) {
		father[find(hasher(a,b))]=find(hasher(c,d));
	}
	sort(k+1,k+cnt+1,cmp);
	for(int i=1;i<=cnt;i++){
		if(find(k[i].l)==find(k[i].r))continue;
		father[find(k[i].l)]=find(k[i].r);
		ans+=k[i].len;
	}
	cout<<ans;
	return 0;
}

Question 06 [ACP2098 新的开始]
Prim 算法妙用
将建发电站的费用直接存进 Distance 数组
跑一遍即可
因为加入连通块需要建发电站或拉电线
直接 Prim 就能 AC

Code
#include<bits/stdc++.h>
using namespace std;
const int N=3078;
int Distance[N],n,leng[N][N],ans;
bool st[N];
struct line{int to,len;};
struct node{
	int id,len;
	bool operator <(const node &rhs)const{
		return len>rhs.len;
	}
};
vector<line> mp[N];

void Prim(){
	priority_queue<node> q;
	for(int start=1;start<=n;start++)q.push({start,Distance[start]});
	while(!q.empty()){
		node top=q.top();
		q.pop();
		if(st[top.id])continue;
	//	printf("%d\n",q.top().id) ;
		st[top.id]=true;
		ans+=top.len;
		for(auto it:mp[top.id]){
			if(Distance[it.to]>it.len){
				Distance[it.to]=it.len;
				q.push({it.to,it.len});
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&Distance[i]);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&leng[i][j]);
	for(int i=1;i<=n;i++)for(int j=1;j<i;j++)mp[i].push_back({j,leng[i][j]}),mp[j].push_back({i,leng[i][j]});
	Prim();
	printf("%d",ans);
	return 0;
} 

Question 07 [ACP2101 Tree]
考虑将所有的白色边都加上一个边权 w 跑 Kruskal
可知 w 越大白色边的数量越小
 
Code


Question 08 [ACP2103 次小生成树]
很好的一道题
让你知道你对最小生成树一无所知 
所以怎么写呢
首先 Kruskal 出最小生成树
然后根据玄学
我们改一条边就是次小生成树
(我无法证明,但当你开始 Coding 之后你就会发现这一切并不重要) 
根据科学
这样会导致一条环的诞生
而显然这条环的其他边就是新连接边的两个端点到他们的 LCA 
而最大值和次大值都可以用倍增法求出
(如果你已经忘了倍增请先打一遍 [P3379 LCA]) 
(如果大佬会树链剖分请 dis 作者并接受作者的 %) 
但次大值怎么求呢 
我们考虑合并两段的最大值和次大值
设两段数列的最大值和次大值分别为 Max_1 Hmax_1 Max_2 Hmax_2
而最终的最大值和次大值为 Ultramax Ultrahmax
显然 Ultramax=max(Max_1,Max_2)
而 Ultrahmax 怎么求呢
当 Max_1==Max_2 时
Ultrahmax=max(Hmax_1,Hmax_2) 
而若 Max_1!=Max_2
Ultrahmax 却不一定等于 min(Max_1,Max_2)
因为可能会存在 min(Max_1,Max_2) < max(Hmax_1,Hmax_2) 的情况 
所以修正后的局部 Code:
int solve(int a,int b,int c,int d){
	if(a==c){
		return max(b,d);
	}else{
		if(min(a,c)<=max(b,d))return max(b,d);
		else return min(a,c);
	}
}

Code
#include<bits/stdc++.h>
using namespace std;
const int N=100087,M=300779,LIM=25,INF=1e9+7;
int n,m;
long long ans;
bool st[M];
struct edge{
	int l,r,len;
	bool operator <(const edge &rhs)const{return len<rhs.len;}
}k[M];
struct tree_node{int length,depth,maxi[35],father[35],hmaxi[35];}tree[N];
inline tree_node _father(tree_node tn,int level){return tree[tn.father[level]];}
struct line{int to,len;};
vector<line> lable[N];
int solve(int a,int b,int c,int d){
	if(a==c){
		return max(b,d);
	}else{
		if(min(a,c)<=max(b,d))return max(b,d);
		else return min(a,c);
	}
}
namespace Father{
	int father[N];
	int find(int x){
		if(father[x]==x)return x;
		return father[x]=find(father[x]);
	}
	void merge(int a,int b){
		a=find(a),b=find(b);
		father[a]=b;
	}
};
void Kruskal(){
	for(int i=1;i<=n;i++)Father::father[i]=i; 
	sort(k+1,k+m+1);
	for(int i=1;i<=m;i++){
		if(Father::find(k[i].l)==Father::find(k[i].r))continue;
		Father::merge(k[i].l,k[i].r);
		ans+=k[i].len; 
		st[i]=true;
		lable[k[i].l].push_back({k[i].r,k[i].len});
		lable[k[i].r].push_back({k[i].l,k[i].len});
	}
}
void Tree(int id,int father,int length,int depth){
	tree[id].father[0]=father;
	tree[id].maxi[0]=length;
	tree[id].hmaxi[0]=-INF;
	tree[id].length=length;
	tree[id].depth=depth;
	for(int i=1;i<=LIM;i++){
		tree[id].father[i]=_father(tree[id],i-1).father[i-1];
		tree[id].maxi[i]=max(tree[id].maxi[i-1],_father(tree[id],i-1).maxi[i-1]);
		tree[id].hmaxi[i]=solve(tree[id].maxi[i-1],tree[id].hmaxi[i-1],_father(tree[id],i-1).maxi[i-1],_father(tree[id],i-1).hmaxi[i-1]);
	}
	for(auto i:lable[id]){
		if(i.to==father)continue;
		Tree(i.to,id,i.len,depth+1);
	}
}
int LCA(int a,int b,int &maxi1,int &hmaxi1,int &maxi2,int &hmaxi2){
	maxi1=-INF,hmaxi1=-INF,maxi2=-INF,hmaxi2=-INF;
	if(tree[a].depth>tree[b].depth)swap(a,b);
	for(int i=LIM;i>=0;i--)if(tree[a].depth<=_father(tree[b],i).depth){
		hmaxi2=solve(maxi2,hmaxi2,tree[b].maxi[i],tree[b].hmaxi[i]);
		maxi2=max(maxi2,tree[b].maxi[i]);
		b=tree[b].father[i];
	}
	if(a==b)return a;
	for(int i=LIM;i>=0;i--)if(tree[a].father[i]!=tree[b].father[i]){
		hmaxi1=solve(maxi1,hmaxi1,tree[a].maxi[i],tree[a].hmaxi[i]);
		maxi1=max(maxi1,tree[a].maxi[i]);
		hmaxi2=solve(maxi2,hmaxi2,tree[b].maxi[i],tree[b].hmaxi[i]);
		maxi2=max(maxi2,tree[b].maxi[i]);
		a=tree[a].father[i];
		b=tree[b].father[i];
	}
	//LCA 不判 deadline , 代码 WA 两行泪 
	hmaxi1=solve(maxi1,hmaxi1,tree[a].maxi[0],tree[a].hmaxi[0]);
	maxi1=max(maxi1,tree[a].maxi[0]);
	hmaxi2=solve(maxi2,hmaxi2,tree[b].maxi[0],tree[b].hmaxi[0]);
	maxi2=max(maxi2,tree[b].maxi[0]);
	return tree[a].father[0];
}
void Handle_Second(){
	int supermaxi=-INF,superhmaxi=-INF,tmp1,tmp2,tmp3,tmp4,answer=INF;
	for(int i=1;i<=m;i++){
		if(st[i])continue; 
		LCA(k[i].l,k[i].r,tmp1,tmp2,tmp3,tmp4);
		supermaxi=max(tmp1,tmp3);
		superhmaxi=solve(tmp1,tmp2,tmp3,tmp4);
		if(supermaxi!=k[i].len){
			answer=min(answer,k[i].len-supermaxi);
		}else{
			answer=min(answer,k[i].len-superhmaxi);
		}
	}
	printf("%lld",ans+answer);
}
int main(){
	int l,r,w;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&l,&r,&w);
		k[i]={l,r,w};
	}
	Kruskal();
	for(int i=0;i<=LIM;i++){
		tree[1].father[i]=-1;
		tree[1].maxi[i]=-INF;
		tree[1].hmaxi[i]=-INF;
	}
	Tree(1,-1,-INF,0);
	Handle_Second();
	return 0;
}
 
posted on   2025ing  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 本地部署 DeepSeek:小白也能轻松搞定!
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 从 Windows Forms 到微服务的经验教训
· 李飞飞的50美金比肩DeepSeek把CEO忽悠瘸了,倒霉的却是程序员
· 超详细,DeepSeek 接入PyCharm实现AI编程!(支持本地部署DeepSeek及官方Dee
点击右上角即可分享
微信分享提示