基本是参照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; }
|