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\)
我們想象一幅圖,假設這幅圖的最小生成樹中有 \(i\) 到 \(j\) 這條邊。我們把這條邊去掉,換成 \(i\) 到 \(k\),\(k\) 到 \(j\) 這兩條邊,由於原圖是一個最小生成樹 ,\(i,j,k\) 在原圖中肯定是通過某些邊相連的,我們設 \(k\) 與 \(q\) 相連,且 \(q\)
那麼我們可以以三維來排序,一共排三次,每次排完都將相鄰的兩個點連一條邊。意思是:我們先以 \(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;
}
總結:
這題刪邊的思想還是比較妙的,碰到時間空間都超限的題目就可以嘗試這種方法,將必定無用的狀態刪去以節省空間與時間。