poj 3241 Object Clustering 曼哈顿最小生成树
题意:
平面上有n个点,如今把他们分成k个集合。使得每一个集合中的每一个点都至少有一个本集合的点之间的曼哈顿距离不大于X,求最小的X。
分析:
转化为求n个点生成全然图的最小生成树的第k大边。接下来有几个重点。
1)依据莫队算法。因为边权是点的曼哈顿距离,每一个点仅仅须要跟周围8个方向中每一个方向近期的点连边。这样算出的图与用全然图算出的最小生成树一样。涉及的边却大大降低。
2)用树状数组维护y右偏45度的近期点,每一个点以y-x的位置,y+x的值放入树状数组。因为每次是查询区间(pos,last)的情况,所以树状数组c[i]的覆盖范围要改成a[i,i+1,...a+2^k-1],k是i二进制末尾0的个数。查询和更新也有对应修改。
3)对最小生成树一个猜想:设一个图的最小生成树的各边按权从小到大排序为a1,a2....an-1,该图随意生成树的各边按权从小到大排序为b1,b2,..bn-1,则ai<=bi(i=1,2,...n-1)。
代码:
//poj 3241 //sep9 #include <iostream> #include <algorithm> const int maxN=10024; using namespace std; int n,k,e; struct P { int x,y,ids; }p[maxN]; struct EDGE { int u,v,w; }edge[maxN*4]; int c[4000],d[4000],fa[maxN]; int lowbit(int x) { return x&(x^(x-1)); } int cmp_p(P a,P b) { if(a.x!=b.x) return a.x<b.x; return a.y<b.y; } int cmp_e(EDGE a,EDGE b) { return a.w<b.w; } void query(int ids,int pos,int val) { pos+=1000; int t_ids=-1,ret=INT_MAX; for(int i=pos;i<4000;i+=lowbit(i)) if(ret>c[i]){ ret=c[i]; t_ids=d[i]; } if(t_ids!=-1){ edge[e].u=ids; edge[e].v=t_ids; edge[e++].w=ret-val; } } void update(int ids,int pos,int val) { pos+=1000; for(int i=pos;i;i-=lowbit(i)) if(val<c[i]){ c[i]=val; d[i]=ids; } } void deal() { memset(c,63,sizeof(c)); sort(p,p+n,cmp_p); for(int i=n-1;i>=0;--i){ int pos=p[i].y-p[i].x; int val=p[i].y+p[i].x; query(p[i].ids,pos,val); update(p[i].ids,pos,val); } } int find(int x) { return x==fa[x]?x:fa[x]=find(fa[x]); } int solve() { if(n==k) return 0; e=0; deal(); for(int i=0;i<n;++i) swap(p[i].x,p[i].y); deal(); for(int i=0;i<n;++i) p[i].y=-p[i].y; deal(); for(int i=0;i<n;++i) swap(p[i].x,p[i].y); deal(); for(int i=0;i<n;++i) fa[i]=i; sort(edge,edge+e,cmp_e); for(int i=0;i<e;++i){ int pa=find(edge[i].u); int pb=find(edge[i].v); if(pa!=pb){ fa[pa]=pb; n--; if(n==k) return edge[i].w; } } } int main() { scanf("%d%d",&n,&k); for(int i=0;i<n;++i){ scanf("%d%d",&p[i].x,&p[i].y); p[i].ids=i; } printf("%d",solve()); }