基本是参照PKKJ写的 Orz
 /**//*
题意:给出n<=100000个点坐标,求MST。边权为点之间的曼哈顿距离
题目给出重要提示:对于一个点O,在45度角度的范围内,最多只有一条连出去的边。

拓展这个提示,对于一个点O,45°的范围内最多只有1条边,8个方向只有8条
8n条边用kruskal可以解决
由于是无向图,只需要找到4个方向即可。通过旋转90°,关于y=x翻转做到这四个方向区域
关于原点对称的区域没有访问过。
那怎么快速找到45°范围内距离(xi,yi)最小的点呢?
由于是45°范围的点,有xj>xi,yj>yi,距离为xj+yj-xi-yi 所以用线段树找x+y最小的点
而且需要是在45°范围内,还有y>yi
先按照w=y-x从小到大排序,w相同的y大的优先
检查每个点,在线段树中找到距离最小的点(没有时为INF)
然后再插入
*/
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>

using namespace std;

const int MAXN = 100010;
const int INF = 1000000000;

struct Edge
  {
int u,v,w;
bool operator<(const Edge &B)const
 {
return w<B.w;
}
};

struct Point
  {
int x,y;
 Point() {}
 Point(int x,int y):x(x),y(y) {}
};

struct SegTree
  {
int left,right;
int Min,id;
};

Point pt[MAXN];
Edge edge[MAXN*10],*pE;
SegTree T[MAXN*3];
int fa[MAXN];
int yy[MAXN],ny[MAXN],idx[MAXN];

inline int dist(Point A,Point B)
  {
return abs(A.x-B.x)+abs(A.y-B.y);
}

bool cmp(const int &a,const int &b)
  {
int w1=pt[a].y-pt[a].x;
int w2=pt[b].y-pt[b].x;
if(w1==w2)return pt[a].y>pt[b].y;
return w1<w2;
}

void build(int p,int left,int right)
  {
T[p].left=left;
T[p].right=right;
T[p].Min=INF;
T[p].id=-1;
if(left==right)return;
int mid=(left+right)>>1;
build(p<<1,left,mid);
build((p<<1)|1,mid+1,right);
}

pair<int,int> query(int p,int y)
  {
if(T[p].right<y)return pair<int,int>(INF,-1);
if(y<=T[p].left)return pair<int,int>(T[p].Min,T[p].id);
return min(query(p<<1,y),query((p<<1)|1,y));
}

void insert(int p,int y,int val,int id)
  {
if(y<T[p].left||T[p].right<y)return;
if(val<T[p].Min)T[p].Min=val,T[p].id=id;//边插入边更新
if(T[p].left==T[p].right)return;//
insert(p<<1,y,val,id);
insert((p<<1)|1,y,val,id);
}

void add(int u,int v,int w)
  {
pE->u=u;
pE->v=v;
pE->w=w;
pE++;
}
void make(int n)
  {
for(int i=0;i<n;i++)
 {
idx[i]=i;
yy[i]=pt[i].y;
}
sort(idx,idx+n,cmp);//对idx[]排序,这样才不会破坏原来的pt[]
sort(yy,yy+n);
int nn=unique(yy,yy+n)-yy;
for(int i=0;i<n;i++)
 {
ny[i]=lower_bound(yy,yy+nn,pt[idx[i]].y)-yy;
}
build(1,0,nn-1);//
for(int i=0;i<n;i++)
 {
pair<int,int> res = query(1,ny[i]);//
if(res.second!=-1)add(idx[i],idx[res.second],dist(pt[idx[i]],pt[idx[res.second]]));
insert(1,ny[i],(pt[idx[i]].x+pt[idx[i]].y),i);
}
}
void solve(int n)
  {
pE=edge;
for(int k=0;k<4;k++)//坐标转换 旋转90; 旋转90+关于y=x翻转; 旋转90
 { //这样得到的4个区域其关于原点的对称的地方都是没访问过的,不会重复
if(k>0)
 {
for(int i=0;i<n;i++)
 {
int x=-pt[i].y,y=pt[i].x;
if(k==2)swap(x,y);
pt[i]=Point(x,y);
}
}
make(n);
}
}

//kruskal
int find(int a)
  {
if(a!=fa[a])fa[a]=find(fa[a]);
return fa[a];
}
void unin(int a,int b)
  {
a=find(a),b=find(b);
if(a==b)return;
fa[a]=b;
}
long long kruskal(int n)
  {
for(int i=0;i<n;i++)
fa[i]=i;
sort(edge,pE);
long long ans = 0;
int k=0;
for(Edge *p=edge;p!=pE&&k<n-1;p++)
 {
if(find(p->u)!=find(p->v))
 {
unin(p->u,p->v);
ans+=p->w;
k++;
}
}
return ans;
}
int main()
  {
//freopen("in","r",stdin);
int n,t=1;
while(scanf("%d",&n),n)
 {
for(int i=0;i<n;i++)
scanf("%d%d",&pt[i].x,&pt[i].y);
solve(n);
printf("Case %d: Total Weight = %lld\n",t++,kruskal(n));
}
return 0;
}
|