题意:
平面上有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());
}