bzoj2177/51nod-1213 曼哈頓距離最小生成樹
演算法分析
對不起我寫這個的時候我們國慶節只放了一天假,所以我精神有點不正常…大家忽略一些不太對的東西即可。
原理分析
曼哈頓距離:對於兩點p1(x1,y1),p2(x2,y2),它們之間的曼哈頓距離為|x1-x2|+|y1-y2|
那麼如何迅速地求曼哈頓距離最小生成樹呢?我們找到一個點p1,然後以它為原點建立座標軸,那麼它只需要與這八個區域裡的每個區域和它曼哈頓距離最近的點連一條邊即可。
這是為什麼呢?我們以R2區域為例,如果有兩個點P2和P3,P3與P1的曼哈頓距離更小,那麼:
dis(指曼哈頓距離):dis(P1,P3)+dis(P3,P2)<=dis(P1,P2)+dis(P2,P3),當且僅當如圖情況時,可以取到等號。所以嘛,基佬紫的P2和原諒綠的P3顯然比P2和滑稽臉的P1關係好些,你們就不要打擾它們的友誼♂啦。
實現分析
那麼根據邊是無向的,對於每一個點P1,我們就只考慮R1,R2,R3,R4啦。
比較好的一點是我們只考慮R1,然後:
1.第一次操作不搞事
2.第二次交換x,y座標
3.第三次將x座標都變成相反數
4.第四次交換x,y座標
如圖所示。
那麼R1區域滿足一個條件,在只考慮R1,R2,R3,R4時(即x2>=x1)對於R1區域的任意點P2
y2-y1>=x2-x1(R3,R4區域裡y2-y1就是負數,x2-x1還是整數)
即y2-x2>=y1-x1
那麼我們每次考慮的就是滿足x2>=x1並且y2-x2>=y1-x1的點咯。
那麼我們找的就是y2-y1+x2-x1最小,即(y2+x2)-(y1+x2)最小的點。
我們可以把所有點先按照橫座標排序,離散它們的y2-x2,然後從左到右依次將點根據y2-x2的值丟進樹狀數組裡,並維護它們x2+y2的最小值。每次查詢y2-x2>=y1-x1的點裡面x2+y2最小的點P2並連邊,最後用kurscal求出最小生成樹即可。
程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<climits>
using namespace std;
const int N=50005;
int n,cnt,m;
int b[N],f[N],mi[N],bh[N];
struct point{int x,y,id;}p[N];
bool cmp1(point a,point b){
if(a.x==b.x)return a.y<b.y;
else return a.x<b.x;
}
struct edge{int x,y,w;}e[N<<3];
bool cmp2(edge a,edge b){return a.w<b.w;}
void init(){
int i;sort(p+1,p+1+n,cmp1);
for(i=1;i<=n;++i)
b[i]=p[i].y-p[i].x,bh[i]=-1,mi[i]=INT_MAX;
sort(b+1,b+1+n);
cnt=1;for(i=2;i<=n;++i)if(b[i]!=b[cnt])b[++cnt]=b[i];
}
int lowbit(int x){return x&(-x);}//以下是樹狀陣列
int find(int x){
int minn=INT_MAX,re=-1;
while(x<=n){
if(mi[x]<minn)minn=mi[x],re=bh[x];
x+=lowbit(x);
}
return re;
}
void up(int x,int num,int ids){
while(x){
if(mi[x]>num)mi[x]=num,bh[x]=ids;
x-=lowbit(x);
}
}
void adde(int x,int y,int w){e[++m].x=x,e[m].y=y,e[m].w=w;}
int dis(int a,int b){return abs(p[a].x-p[b].x)+abs(p[a].y-p[b].y);}
int ff(int x){//並查集查詢操作
if(f[x]==x)return x;
f[x]=ff(f[x]);return f[x];
}
void kurscal(){//最小生成樹
int i,r1,r2,ans=0,js=0;
for(i=1;i<=n;++i)f[i]=i;
sort(e+1,e+1+m,cmp2);
for(i=1;i<=m;++i){
r1=ff(e[i].x),r2=ff(e[i].y);
if(r1!=r2)f[r1]=r2,ans+=e[i].w,++js;
if(js==n-1)break;
}
printf("%d",ans);
}
int main(){
int i,cas,kl,x;
scanf("%d",&n);
for(i=1;i<=n;++i)
scanf("%d%d",&p[i].x,&p[i].y),p[i].id=i;
for(cas=1;cas<=4;++cas){
if(cas==2||cas==4)
for(i=1;i<=n;++i)swap(p[i].x,p[i].y);
else if(cas==3)
for(i=1;i<=n;++i)p[i].x=-p[i].x;
init();
for(i=n;i>=1;--i){
kl=lower_bound(b+1,b+1+cnt,p[i].y-p[i].x)-b;
x=find(kl);
if(x!=-1)adde(p[i].id,p[x].id,dis(i,x));
up(kl,p[i].x+p[i].y,i);
}
}
kurscal();
return 0;
}