1. 程式人生 > 其它 >COCI2009/2010 Contest#7 D

COCI2009/2010 Contest#7 D

COCI2009/2010 Contest#7 D

題意:在小C統治宇宙之後(純屬YY),他決定建立一座時空隧道網路來連線不同的星球和星系。已知小C的宇宙共有 \(N\) 個星球,他們的座標用3維 \((x,y,z)\) 的形式給出,而任意兩個星球之間建立時空隧道的代價為:

\[Cost_{a,b}=\min(\lvert x_a-x_b\rvert,\lvert y_a-y_B\rvert,\lvert z_a-z_b\rvert) \]

​ 其中 \(x,y,z\) 分別表示星球的座標。小C想建立 \(N-1\) 條時空隧道,剛好連線所有星球,並且需要建立隧道的花費盡可能小。作為他的總工程師,你能算出最小的代價麼?\((1\le N\le10^5)\)


​ 建立 \(N-1\) 條邊,剛好連線所有星球,很明顯的一個 最小生成樹 的題目。我們可以用 Kruskal 來解決這個問題。但是 \(N\) 太大了一共有 \(\dfrac{N\times(N-1)}{2}\) 條邊,如果全部存下來,這個數量無論是時間還是空間我們都是無法接受的。我們必須考慮優化,既需要優化空間又需要優化時間,那麼最簡單直接的方法就是刪邊,注意到,我們一共只需要 \(N-1\) 條邊,所以我們可以把一些肯定不可能的邊刪去。思考一下,假如我們要用一條從 \(i\)\(j\) 的邊,並且他們的邊權 \(w\)\(\lvert x_i-x_j\rvert\),如果存在一個點 \(k\)

使得 \(k\) 滿足 \(\min(x_i,x_j)\le x_k\le\max(x_i,x_j)\) 那麼是不是我們可以以同樣的代價連線 \(i,k\)\(j,k\)。如果這麼連線,不僅代價相同而且產生的一個聯通塊更大了。但是這種情況一定是最優的嗎,畢竟我們多花了一條邊去連線。

​ 我們想象一幅圖,假設這幅圖的最小生成樹中有 \(i\)\(j\) 這條邊。我們把這條邊去掉,換成 \(i\)\(k\)\(k\)\(j\) 這兩條邊,由於原圖是一個最小生成樹 ,\(i,j,k\) 在原圖中肯定是通過某些邊相連的,我們設 \(k\)\(q\) 相連,且 \(q\)

通過一系列邊與 \(i,j\) 相連。顯然,我們去掉 \(k\)\(q\) 相連的這條邊,那麼圖中又只有 \(n-1\) 條邊了。顯然,這幅圖還是一幅連通圖,而且代價會更小(或者相等)。於是我們可以得出結論:在本題的條件下,代價相同的情況下,所能產生的聯通快越大越好

​ 那麼我們可以以三維來排序,一共排三次,每次排完都將相鄰的兩個點連一條邊。意思是:我們先以 \(x\) 排序(使用結構體連編號一起排序),然後對於任意的 \(i\) 我們連一條從 \(id_i\)\(id_{i-1}\),邊權為 \(\lvert x_i-x_{i-1}\rvert\) 的邊,然後再以 \(y\) 排序,以此類推。我們這麼連線的邊可能有些權值不符合題設,但是連出來的邊一定都大於題設邊的權值。如果我們使用了不符合題設的邊作為答案,那麼它一定可以被符合題設的邊替代。所以算出來的答案不會偏大。具體細節看看程式碼。

程式碼如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 1e5+5;
int n;
struct node
{
	ll x;
	int id;
	bool operator < (const node &a)const
	{
		return x<a.x;
	}
}x[MAXN],y[MAXN],z[MAXN];
struct edge
{
	ll w;
	int u,v;
	bool operator < (const edge &x)const
	{
		return w<x.w;
	}
}e[MAXN<<2];
int tot;
void add(int i,int j)
{
	e[++tot].w=abs(x[i].x-x[j].x);
	e[tot].u=x[i].id,e[tot].v=x[j].id;
	
	e[++tot].w=abs(y[i].x-y[j].x);
	e[tot].u=y[i].id,e[tot].v=y[j].id;
	
	e[++tot].w=abs(z[i].x-z[j].x);
	e[tot].u=z[i].id,e[tot].v=z[j].id;
}
int f[MAXN];
int find(int x)
{
	if(x==f[x]) return x;
	else return f[x]=find(f[x]);
}
ll kruskal()
{
	ll ans=0;
	sort(e+1,e+1+tot);
	for(int i=1;i<=n;++i) f[i]=i;
	for(int i=1;i<=tot;++i)
	{
		int u=e[i].u,v=e[i].v;
		ll w=e[i].w;
		if(find(u)==find(v)) continue;
		ans+=w;
		f[find(u)]=find(v);
	}
	return ans;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%lld %lld %lld",&x[i].x,&y[i].x,&z[i].x);
		x[i].id=y[i].id=z[i].id=i;
	}
	sort(x+1,x+1+n);sort(y+1,y+1+n);sort(z+1,z+1+n);
	for(int i=2;i<=n;++i)
		add(i,i-1);
	printf("%lld",kruskal());
	return 0;
}

總結:

​ 這題刪邊的思想還是比較妙的,碰到時間空間都超限的題目就可以嘗試這種方法,將必定無用的狀態刪去以節省空間與時間