好消息,为庆祝自己暑假上蓝,并成功晋级为参赛队员。我决定在这个暑假集训中写一篇研究性报告,像那些国家集训队的人那样,当然质量没有那么高。我假装网上没有直接完整的关于曼哈顿最小生成树资料。于是自己就想做整理和详细解释的工作。后文会放上自己参考的blog,喝水不忘挖井人。
摘要:
曼哈顿最小生成树,是把最小生成树中两点直线距离的条件改成了在坐标系下的曼哈顿距离的条件。
结论:
以一个点为原点建立直角坐标系,在每45度内只会向距离该点最近的一个点连边。
算法过程:
我直接就按照算法runing的顺序来解释了,总的来说,这个算法,第一步,在给定的点中,连出最有用的边,第二步就直接利用克鲁斯卡尔(Kruskal)算法,求出最小的费用。
建边时,每个点的45度就有八个区域,但是,由于这是一个无向图,所以只用考虑四个区域。我们假定x轴正半轴向上45’的区域为R1,逆时针为R2,R3,R4,R5,R6,R7,R8。第一次我们可以处理R2区域。第二次,通过交换每个点自己的x坐标和y坐标,R1和R2交换,R3和R8交换,这样,第一次对R2区域的操作,现在就是对R1的操作。第三次,我们把每个点的x坐标交换一下正负号,这样,第一次对R2区域的操作,现在就是对R8的操作。第四次,再次交换每个点的x坐标和y坐标,这样,第一次对R2区域的操作,现在就是对R7的操作。这样单边四个区域我们就枚举完了。
for(int dir = 1; dir <= 4; dir++){
if(dir==2 || dir==4)
for(int i=1; i<=n; i++)swap(p[i].x,p[i].y);
else if(dir == 3)
for(int i=1; i<=n; i++)p[i].x = -p[i].x;
...
}
下面我们来考虑,怎么处理第一个区域R2的信息。
我们每次处理R2区域的时候,在某个点A(x0,y0)的这个区域内的点B(x1,y1)满足$x1≥x0$且$y1-x1>y0-x0$。这里对于边界我们只取一边,但是操作中两边都取也无所谓。那么$|AB|=y1-y0+x1-x0=(x1+y1)-(x0+y0)$。在A的区域内距离A最近的点也即满足条件的点中 x+y 最小的点。因此我们可以将所有点按x坐标排序,再按y-x离散,用线段树或者树状数组维护大于当前点的y-x的点中,最小的x+y对应的点(也就是维护区间最小值)。时间复杂度O(NlogN)。
我再说一下,就是,这里看的不是一个点,而是考虑每次在区间中加入一个点A,我们要给这个点找到距离它最近的满足条件的点B(就是点B 在点A的右上方,即B点的斜率大于A点的斜率,且距离A不能再近了),建立一条边。然后我们要把A的信息留在区间,留着给下一个点用,要留的信息就是A的x坐标与y坐标之和,因为,
$|AB|=y1-y0+x1-x0=(x1+y1)-(x0+y0)$。在A的区域内距离A最近的点也即满足条件的点中x+y最小的点。
for(int i = n; i >= 1; i--){
int pos = query(a[i]);
if(pos!=-1){
addedge(p[i], p[pos]);
}
update(a[i], p[i].x + p[i].y, i);
}
这里的query和update我用的是树状数组,原来树状数组还可以怎么用,不是单点更新和区间求和。对于树状数组中的每一个点,完全可以向后得到后面传过来的信息,向前传去需要更新的信息。还很快,每次不需要O(n),只用O(logN).
建边完成之后,就可以直接用克鲁斯卡尔(Kruskal)算法了。
具体用途;
poj3241Object Clustering
http://poj.org/problem?id=3241
这个题目的意思是:
给定n个点,允许分成k块,在最优秀的分法中,最大的一条边(长度用曼哈顿距离表示)最小可以是多少。
这个题目可以转化成先建立一个最小生成树,去掉k-1条边之后,最大的边长度是多少。
注意这道题g++不可过,换成c++就过了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <list>
#include <cstdlib>
#include <iterator>
#include <cmath>
#include <iomanip>
#include <bitset>
#include <cctype>
using namespace std;
//#pragma GCC optimize(3)
//#pragma comment(linker, "/STACK:102400000,102400000") //c++
#define lson (l , mid , rt << 1)
#define rson (mid + 1 , r , rt << 1 | 1)
#define debug(x) cerr << #x << " = " << x << "\n";
#define pb push_back
#define pq priority_queue
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll ,ll > pll;
typedef pair<int ,int > pii;
typedef pair<int ,pii> p3;
//priority_queue<int> q;//这是一个大根堆q
//priority_queue<int,vector<int>,greater<int> >q;//这是一个小根堆q
#define fi first
#define se second
//#define endl '\n'
#define OKC ios::sync_with_stdio(false);cin.tie(0)
#define FT(A,B,C) for(int A=B;A <= C;++A) //用来压行
#define REP(i , j , k) for(int i = j ; i < k ; ++i)
//priority_queue<int ,vector<int>, greater<int> >que;
const ll mos = 0x7FFFFFFFLL; //2147483647
const ll nmos = 0x80000000LL; //-2147483648
const int inf = 0x3f3f3f3f;
const ll inff = 0x3f3f3f3f3f3f3f3fLL; //18
const double PI=acos(-1.0);
template<typename T>
inline T read(T&x){
x=0;int f=0;char ch=getchar();
while (ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x=f?-x:x;
}
// #define _DEBUG; //*//
#ifdef _DEBUG
freopen("input", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
/*-----------------------show time----------------------*/
const int maxn = 1e5+9;
struct Point{
int x,y,id;
bool operator < (const Point &q)const{
if(x == q.x)return y < q.y;
return x < q.x;
}
}p[maxn];
struct BITnode{
int w,p;
}BITsum[maxn];
struct Edge{
int u,v,dis;//曼哈顿距离
bool operator < (const Edge & e)const {
return dis < e.dis;
}
} e[maxn << 3 | 1];
int tot = 0,fa[maxn];
int find(int x){
if(fa[x] == x)return x;
else return fa[x] = find(fa[x]);
}
void addedge(Point a,Point b){
tot ++;
e[tot].u = a.id;
e[tot].v = b.id;
e[tot].dis = abs(a.x - b.x) + abs(a.y-b.y);
}
int lowbit(int x){
return x & (-x);
}
int a[maxn],n,k,*ix[maxn];
bool cmp(int * x,int * y){
return (*x) < (*y);
}
int query(int x){
int mx = inf,p = -1;
while(x <= n){
if(BITsum[x].w < mx){
mx = BITsum[x].w;
p = BITsum[x].p;
}
x += lowbit(x);
}
return p;
}
void update(int x,int w,int p){
while(x > 0){
if(BITsum[x].w > w){
BITsum[x].w = w;
BITsum[x].p = p;
}
x -= lowbit(x);
}
}
int main(){
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++){
scanf("%d%d", &p[i].x, &p[i].y);
p[i].id = i;
}
// 建立边
tot = 0;
for(int dir = 1; dir <= 4; dir++){
if(dir==2 || dir==4)
for(int i=1; i<=n; i++)swap(p[i].x,p[i].y);
else if(dir == 3)
for(int i=1; i<=n; i++)p[i].x = -p[i].x;
sort(p + 1, p + n + 1);
//这里离散的操作我觉得也很厉害。
for(int i=1; i<=n; i++)a[i] = p[i].y - p[i].x, ix[i] = &a[i];
sort(ix + 1, ix + n + 1, cmp);
for(int i=1; i<=n; i++) *ix[i] = i;
for(int i = 1; i <= n; i++)
BITsum[i].w = inf,BITsum[i].p = -1;
for(int i = n; i >= 1; i--){
int pos = query(a[i]);
if(pos!=-1){
addedge(p[i], p[pos]);
}
update(a[i], p[i].x + p[i].y, i);
}
}
//库鲁斯卡尔 算法 开始
sort(e + 1,e + tot + 1);
for(int i=1; i<=n; i++)fa[i] = i;
int cnt = n - k,c1 = 0,ans = 0;
for(int i=1; i<= tot; i++){
if( find(e[i].u) != find(e[i].v) ){
int px = find(e[i].u);
int py = find(e[i].v);
fa[px] = py;
c1++;
if(c1 == cnt){ ans = e[i].dis;break;}
}
}
printf("%d\n", ans);
return 0;
}
POJ3241