洛谷 P3366 【模板】最小生成樹(並查集+壓縮路徑(縮短到最高上一級的步驟))

小黑哎呀發表於2020-12-23

洛谷 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
*/

相關文章