[題解]逃離地球

Sinktank發表於2024-07-05

題意簡述

有一個星系,共有\(n*m\)個星球,排成\(n\)\(m\)列。

初始星球之間沒有道路。接下來給定\(P\)種魔法\(1\)\(Q\)種魔法\(2\)

  • 魔法\(1\):第\(i\)種魔法用\(a_i,b_i,c_i\)描述。表示你可以任選星系的一行,在第\(a_i\)和第\(b_i\)個星球之間建立一條航道,消耗\(c_i\)的能量。
  • 魔法\(2\):第\(i\)種魔法用\(x_i,y_i,z_i\)描述。表示你可以任選星系的一列,在第\(x_i\)和第\(y_i\)個星球之間建立一條航道,小號\(z_i\)的能量。

詢問要想讓所有星球互相連通,最少需要消耗多少太陽能。

思路簡述

根據貪心的思想,我們如果找到一種魔法消耗很小,那我們會想到對所有行/列都使用這種一次魔法。

於是想到使用最小生成樹來解決。把行標和列標看作節點,魔法看作邊,魔法消耗看作邊權。那麼就相當於對這樣一個擁有\(2\)棵樹的森林跑一次最小生成樹。需要注意的是,不能先跑行再跑列,而是需要一同排序,一併處理。原因就是行數和列數是在變化的(比如我們已經對所有列都使用了魔法,連線了\(1,2\)行,那麼我們可以理解為我們刪掉了\(1\)行。因為只要能連線到第\(2\)行的一定也能連線到第\(1\)行。所以我們再對行使用魔法的時候就可以少考慮一行,即這一組魔法總消耗應該等於 單次消耗\(\times(\)當前行數\(-1)\))。

正確性證明:
設有兩邊權值分別是\(u,v\),而\(u>v\)。那麼

  • 顯然\(u,v\)同為魔法\(1\)或同為魔法\(2\)時先用\(v\)最優。

  • 如果\(u,v\)一個是魔法\(1\),一個是魔法\(2\),則先用\(u\)和先用\(v\)的花費如下:

    • 先用\(u\)\(u*n+v*(m-1)=u*n+v*m-v\)
    • 先用\(v\)\(v*m+u*(n-1)=u*n+v*m-u\)

    故還是先用\(v\)更優。

程式碼使用Kruskal實現,需要用並查集。為了行標和列標不衝突所以行標節點統一\(+m\)儲存,因此注意空間需要開\(2\)倍。

#include<bits/stdc++.h>
#define N 100010
#define M 100010
#define int long long
using namespace std;
struct edge{
	int u,v,w;
}edges[2*M];//開兩倍是因為行列都要存
bool cmp(edge a,edge b){return a.w<b.w;} 
int n,m,p,q,fa[2*N],ans;
int find(int x){
	if(fa[x]==x) return x;
	else return fa[x]=find(fa[x]);
}
signed main(){
	cin>>n>>m>>p>>q;
	for(int i=1;i<=n+m;i++) fa[i]=i;
	for(int i=1;i<=p;i++){//選行連列 
		cin>>edges[i].u>>edges[i].v>>edges[i].w;
	}
	for(int i=p+1;i<=p+q;i++){//選列連行 
		cin>>edges[i].u>>edges[i].v>>edges[i].w;
		edges[i].u+=m,edges[i].v+=m;
	}
	sort(edges+1,edges+1+p+q,cmp);
	int nn=n,mm=m;
	for(int i=1,cnt=0;i<=p+q;i++){
		int u=find(edges[i].u),v=find(edges[i].v);
		if(u==v) continue;
		cnt++;
		fa[u]=v;
		if(u<=m) ans+=nn*edges[i].w,mm--;//列節點
		else ans+=mm*edges[i].w,nn--;//行節點 
		if(cnt==n+m-2) break;
	}
	cout<<ans;
	return 0;
}

相關文章