洛谷 P3366 【模板】最小生成树(并查集+压缩路径(缩短到最高上一级的步骤))

洛谷 P3366 【模板】最小生成树

菜鸟生成记(12)

话说这个算法思想应该属于K开头的算法吧;这个算法还挺好的,比P开头的那个算法好理解;

我总以为我可以做一些不寻常的事(比如:在不了解两种最小生成树的算法前,自己试图写一下,结果超时60%);菜鸟就是菜鸟,好好刷水题吧!
暴力解决不了任何问题,只能解决自己(暴力的思想要改改了)

AC代码后附有O(n^3)的代码;

#include<bits/stdc++.h>
using namespace std;
const int N=5e+3+10;//看准数据范围,(我第一次提交时,RU了4个点;数组开小了) 
const int M=2e+5+10;
int pre[N]={0};
struct st{
	int v1,v2;//两个顶点 
	int w;//边权 
	st(){v1=v1=w=0;}//初始化(在这一题没什么用,但是定义时初始化是个好习惯噢!) 
}s[M];
int cmp(const st &a,const st &b)
{//sort的排序条件(比较权值决定传值的两个元素是否交换) 
	return a.w<b.w;
}
int find(int x)
{//找祖先 
	int t=x;
	while(pre[t]!=t)//只有祖先元素的值是自己(主函数里pre数组初始化每个数组元素都是自己的祖先) 
	{
		t=pre[t];//循环前往上一辈 
	}
	return t;//返回祖先 
}
int main()
{
	int n,m;//n:结点数 m:边数 
	int sum=0;//最小代价计数 
	int k=0;//最小生成树加入边数计数(n-1)
	//都一条边就有环路,少一条就不连通 
	int x,y,w;
	int t1,t2;
	cin>>n>>m;
	for(int i=1;i<=n;i++)//初始化 
	{//每个元素都是自己的祖先 
		pre[i]=i;
	}
	for(int i=0;i<m;i++)
	{//记录每条边的两个顶点和权值 
		cin>>x>>y>>w;
		s[i].v1=x,s[i].v2=y;
		s[i].w=w;
	}
	sort(s,s+m,cmp);//按权值从小到大排序 
	/*
	从小到大排序后,循环时就可以优先选择边的权值小的边;
	然后循环中就不用考虑这个边是否是可加入最小的边;
	只需要判断加入这个边是否会形成环路; 
	*/ 
	for(int i=0;i<m;i++)
	{
		x=s[i].v1,y=s[i].v2;
		w=s[i].w;
		t1=find(x);//找祖先 
		t2=find(y);//找祖先 
		if(t1!=t2)//祖先不一样(生成树加入该边不会构成环路) 
		{
			k++;//生成树边数记录 
			sum+=w;//生成树权值累加 
			int f1,f2;
			pre[y]=t1;//让y认x的祖先为y的祖先 
			pre[t2]=t1;//让y的祖先也认x的祖先为自己的祖先 
			/*
			这就是压缩路径,使每个元素找到祖先(最上面的一级)的步骤减少;
			但是这样就找不到爸爸(离自己最近的上一级);因为所有元素都在尽可能
			认祖先为爸爸,原来的爸爸就找不到了; 不过不影响这一的运作过程; 
			*/
			if(k==n-1)//最小生成树形成,循环结束 
			break;
		}
		else//祖先一样,x,y属于一棵树(连通分支) 
		continue;
	}
	cout<<sum<<endl;
	return 0;
}

暴力广搜超时60%(O(n^3))

#include<bits/stdc++.h>
using namespace std;
typedef struct st ak;
const int N=5e+3+10;
const int M=2e+5+10;
int e[N][N]={0};//邻接矩阵 
struct st{
	int v1,v2,w;
	st(){v1=v2=w=0;}
}s[M];
int n;
int m;//结点数 
int cmp(ak a,ak b)
{
	return a.w<b.w;
}
int find(int x,int y)
{//广搜从一个顶点出发
//从x出发能够到达y,说明x,y在一个连通分量中,return 0;
//否则 return 1; 
	int q[N]={0};
	int f=0,r=0;
	int book[N]={0};
	q[r++]=x;
	book[x]=1;
	while(f!=r)
	{
		int k=q[f++];
		for(int i=1;i<=m;i++)
		{
			if(!book[i]&&e[k][i]!=0)
			{
				book[i]=1;
				if(i==y)
				return 0;
				q[r++]=i;
			}
		}
	}
	return 1;
}
int main()
{
	int sum=0;
	int k=0;
	cin>>m>>n;//m结点数,n边数 
	for(int i=0;i<n;i++) 
	{
		int x,y,w;
		cin>>x>>y>>w;
		s[i].v1=x,s[i].v2=y,s[i].w=w;
	} 
	sort(s,s+n,cmp);//按权值从小到大排序 
	for(int i=0;i<n;i++)
	{
		int x,y,w;
		x=s[i].v1,y=s[i].v2,w=s[i].w;
		//printf("[%d,%d,%d]->\n",x,y,w);
		if(x==y)//自环 跳过 
		continue;
		if(find(x,y)==1)//判断x,y是否在一个联通分量中 
		{ 
			e[x][y]=w;
			e[y][x]=w;
			sum+=w;
		}
		else//在一个连通分量中,重边,选择权值最小的,作为边权 
		{//新权值小于原有权值 
		//更新x->y和y->x的权值 
			if(w<e[x][y])
			{
				sum-=e[x][y];
				e[x][y]=e[y][x]=w;
				sum+=w;
			}
		}
	}
	cout<<sum<<endl;
	return 0;
}
/* 
5 8
1 3 4
1 2 1
1 4 5
2 5 2
2 4 4
3 5 3
4 5 2
3 5 1
*/
/*
5 18
2 4 276
3 3 435
3 4 608
2 4 860
1 2 318
1 3 547
5 4 419
2 5 98
1 5 460
5 3 399
3 5 240
3 2 733
3 3 903
4 2 909
5 2 206
3 4 810
2 1 115
2 3 419
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值