题目地址
题目就是给你n个点 求n个点的曼哈顿距离的最小生成树 输出所有边中第k大的的边的权重。
n个点那么有有 n∗(n−1) 条边如果采用朴素的prim算法建边就是 O(n2) 的复杂度,我们来考虑一下曼哈顿距离的特殊性,其实不是所有的边都需要,在建边的时候就可以去掉很多多余的边。
如图,对于给定的一些点我们选取一个点那么可以以这个点为原点建立一个直角坐标系,然后把每一个象限划分为两个区域,对于每一个区域我们的单独考虑,例如我们来考虑R1区域,有A,B两个点原点是O,那么如果O点要向这个区域内的点连边,最多只能建一条边。这是为什么呢?
首先来看如果能够建两条或者以上的边,比如OA和OB那么,OAB构成一个三角形,角AOB小于60度,角OAB大于90度,那么OB一定是三角形的最长边,也就是做|OB|>|AB|,也就是说,我选择建立OA和AB这两条边是比OA和OB这两条边要更优的。所以只会连一条边。
然后问题就变成了在R1区域内找一条距离O最短的点建边就可以了,我们设 O(x0,y0),A(x1,y1) 那么A点满足 y1−y0>x1−x0,x1>x0 移项得 y1−x1>y0−x0,x1>x0 ,这时候的曼哈顿A与O的曼哈顿距离表示为 (y1−y0)+(x1−x0)⇒(y1+x1)−(x0+y0) ,也就是求满足 y1−x1>y0−x0,x1>x0 的最小的 y1+x1 。
这里可以用树状数组来离散y-x*(一个能够查询区间x到上限最值得树状数组),就可以在 O(nlogn) 复杂度内求出可能的边。
具体做法:
首先将所有的点按照x从小到大排列好,然后把每个点的y-x算出来的复制两份分别存到两个数组a1,a2当中,把a2排序后去除相同的数。
然后从大到小遍历每个点,在当前点的时候,a1中y-x和当前点是对应的,这个时候二分从a2中找到这个y-x的排名,也就是这个点的y-x大小排在第几位,假如是第pos位,那么我们在树状数组当中找pos到最大区间的范围内的最小x+y的点的编号,然后当前点和这个点建边(大家到这里可能有点懵,为什么能找到?我们继续往下看)然后把当前点的x+y的值插入到数组当中pos位置,其实大家应该就能理解为什么能查到了,应为我们是插入到pos的位置 那么对于一起的比当前y-x要大的点肯定是插入到pos的后面的位置的,也就是说pos后面的位置全部是y-x比当前还要大的点,那么区间查询的时候也就满足了这个区间所有的y-x比当前的要大,然后存的时候x+y,所以是找一个这个区间的最小值(注意记录点的编号)。
然后对于其他的区间R2…其实根据对称性 我们只需要找枚举四个区间就可以了,应为另外四个刚好相反,是重复的。这里我们枚举R1,R2,R3,R4。其实每个区间可以根据变换把它移动到R1区间,例如要枚举R2,可以把所有的点绕y=x翻转一下,就是交换所有点的y和x,然后其他区间同理,可以变换过来。
这样我们就得到了所有可能的边,然后用Kruskal算法在可能的边中找出最小树的边。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define clr(x) memset(x,0,sizeof(x))
#define clm(x) memset(x,0x3f,sizeof(x))
using namespace std;
#define LL long long
const int N = 100005;
struct BIT
{
int arr[N];
int Id[N];
void _init()
{
for(int i = 0;i1<<30,Id[i] = -1;
}
inline int lowbit(int k)
{
return k&(-k);
}
void Update(int pos,int val,int id)
{
while(pos>0)
{
if(arr[pos]>val)
{
arr[pos] = val;
Id[pos] = id;
}
pos-=lowbit(pos);
}
}
int read(int pos,int m)
{
int minval = 1<< 30;
int ans = -1;
while(pos<=m)
{
if(minval>arr[pos])
{
minval = arr[pos];
ans = Id[pos];
}
pos += lowbit(pos);
}
return ans;
}
}B;
struct POS{
int x,y;
int id;
bool operator<(const POS &p)const
{
if(x!=p.x)
return xreturn ystruct Edge{
int s,e,d;
bool operator<(const Edge &ed)const
{
return d2],ansEdg[N<<2];
int ecnt;
int acnt;
void Calculate(int n) // 在R1区间中建边
{
sort(p,p+n);
int a[N],b[N];
for(int i = 0;iint m = unique(b,b+n)-b;
B._init();
for(int i = n-1;i>=0;i--)
{
int pos = lower_bound(b,b+m,a[i])-b+1;
int ans = B.read(pos,m);
if(ans!=-1)
{
edg[ecnt].s = p[i].id;
edg[ecnt].e = p[ans].id;
edg[ecnt].d = abs(p[i].x-p[ans].x)+abs(p[i].y-p[ans].y);
ecnt++;
}
B.Update(pos,p[i].x+p[i].y,i);
}
}
int fa[N],k;
int _find(int x)
{
return fa[x] = (fa[x]==x?x:_find(fa[x]));
}
void manhattan(int n)
{
for(int dir = 0;dir<4;dir++)
{
if(dir==1 || dir ==3) // 变换区间
{
for(int i = 0;ielse if(dir==2) // 变换区间
{
for(int i = 0;i/********Kruskal**********/
acnt = 0;
for(int i = 0;i<=n;i++)
fa[i] = i;
sort(edg,edg+ecnt);
int cnt = n-k;
for(int i = 0;iint s = edg[i].s;
int e = edg[i].e;
int fs = _find(s);
int fe = _find(e);
if(fs!=fe)
{
cnt--;
fa[fs] = fe;
ansEdg[acnt].s = s;
ansEdg[acnt].e = e;
ansEdg[acnt].d = edg[i].d;
acnt++;
}
}
}
int main()
{
int n;
while(scanf("%d%d",&n,&k)!=EOF && n)
{
ecnt = 0;
acnt = 0;
for(int i = 0;iscanf("%d%d",&p[i].x,&p[i].y);
p[i].id = i;
}
manhattan(n);
sort(ansEdg,ansEdg+acnt);
printf("%d\n",ansEdg[acnt-k].d);
}
return 0;
}