专栏文章
P2076 曼哈顿距离最小生成树 题解
P2076题解参与者 2已保存评论 1
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 1 条
- 当前快照
- 1 份
- 快照标识符
- @mipkou7g
- 此快照首次捕获于
- 2025/12/03 13:34 3 个月前
- 此快照最后确认于
- 2026/01/20 18:21 4 周前
本文在结论的证明上参考了:曼哈顿距离最小生成树 - 博客园,在此表示对作者的感谢。
下文中对于平面中的两个点 和 ,记 表示 和 的曼哈顿距离,即 。
对于完全图最小生成树一类题,一种直接的想法是使用蓝莓算法解决。但是我们将会在这里介绍一种直接使用 Kruskal 的解法。
观察到本题边数很多,而 Kruskal 是基于边的贪心,所以只有一条道路——去掉必然不会被选择的边,只保留少量的边求解。
先给出结论:对于每个点 ,如下图将平面划分为 个 的区域( 为四条分割线的交点,其中两条分割线分别与 轴与 轴平行),将该点与每个区域中离它最近的任意一个点连边即可。下证为什么这样的连边方式是最优的。

证明:
设 为原点 ,在上面的 中考虑两个点 (其他区域同理;由于在区域 中所以满足 和 ),不妨认为 。根据最小生成树的性质,对于原图的一个三元环,其中的最长边一定不会被选入最小生成树,故只需证:,即 可以被忽略。分类讨论 四者的关系:
- :与 矛盾;
- :此时 ,故 。如果 则有 ,故有 ,,与 矛盾;
- :与 的情况同理;
- :此时 ,始终满足 。
综上,该结论成立。
现在的问题在于,如何找出这些边。可以观察到一个事情,事实上只需要考虑区域 中的点进行连边(因为每条边必然有一个点 坐标较小,相当于将其与 坐标较大的连边),常数能够除以 。
考虑如何快速找到单个区域中离一个点 最近的点:以区域 为例,对于一个点 ,由于在区域 所以满足 ,又因为 ,所以需要最小化 。
对于这一类二维偏序问题,考虑按其中一维排序,在数据结构上维护另一维。观察到需要维护的是最值,又由于需要求值的下标范围是一个后缀,所以直接上树状数组维护。
求解除了 以外的其他区域很简单,只需要合理地组合使用下面的操作进行坐标变换即可:
- 交换 坐标和 坐标,即将一个点关于直线 轴对称;
- 将 坐标取相反数,即将一个点关于直线 轴对称。
时间复杂度 。
参考代码(GNU C++ 17):
CPP#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef tuple<int,int,int> tpi;
namespace IAOI_lib{
template<typename T> class dsu{
private:
vector<T> a;
vector<int> s;
public:
dsu(){
a.resize(200000),s.resize(200000,1);
iota(a.begin(),a.end(),0);
}
dsu(int n){
a.resize(n),s.resize(n,1);
iota(a.begin(),a.end(),0);
}
T leader(T x){
return a[x]==x?x:a[x]=leader(a[x]);
}
inline int size(T x){
return s[leader(x)];
}
inline void merge(T x,T y){
x=leader(x),y=leader(y);
if(x==y)return;
if(s[x]>s[y])swap(x,y);
s[y]+=s[x],a[x]=y;
}
inline bool same(T x,T y){
return leader(x)==leader(y);
}
}; // 并查集模板
template<typename T,T(*op)(T,T),T(*e)()> class fenwick_tree{
private:
vector<T> t;
public:
fenwick_tree(){
t.resize(200000,e());
}
fenwick_tree(int n){
t.resize(n,e());
}
inline int lowbit(int x){
return x&-x;
}
inline void add(int p,T d){
t[p]=op(t[p],d),p++;
while((p-=lowbit(p))>0)
t[p-1]=op(t[p-1],d);
}
inline T suf_sum(int p){
T s=t[p++];
while((p+=lowbit(p))<=t.size())
s=op(s,t[p-1]);
return s;
}
}; // 维护后缀信息的树状数组模板
inline pii mn(pii x,pii y){return x<y?x:y;}
inline pii id(){return make_pair(2e9,-1);}
inline pair<ll,vector<pii> > mst(vector<tpi> e,int n=1){
vector<pii> e2;
for(auto [u,v,w]:e)n=max({n,u+1,v+1});
sort(e.begin(),e.end(),[](tpi x,tpi y){
return get<2>(x)<get<2>(y);
});
dsu<int> d(n); ll r=0;
for(auto [u,v,w]:e)
if(!d.same(u,v))d.merge(u,v),e2.emplace_back(u,v),r+=w;
return make_pair(r,e2);
} // Kruskal 求解最小生成树
inline pair<ll,vector<pii> > manhattan_mst(vector<pii> p){
vector<tpi> e;
for(int r1=0;r1<2;r1++){
for(auto &[x,y]:p)x=-x;
for(int r2=0;r2<2;r2++){
vector<int> a(p.size()),b;
for(auto &[x,y]:p)
swap(x,y),b.emplace_back(x);
sort(b.begin(),b.end());
b.erase(unique(b.begin(),b.end()),b.end());
auto get=[&](int x){
return lower_bound(b.begin(),b.end(),x)-b.begin();
}; // 对坐标离散化
iota(a.begin(),a.end(),0);
sort(a.begin(),a.end(),[&](int x,int y){
int dx=p[x].second-p[x].first,dy=p[y].second-p[y].first;
return dx==dy?p[x].first>p[y].first:dx>dy;
}); // 对第一维排序
fenwick_tree<pii,mn,id> t(b.size());
for(int i=0;i<p.size();i++){
auto [x,y]=p[a[i]];
int w=get(x);
auto [s,j]=t.suf_sum(w);
if(~j)e.emplace_back(a[i],a[j],s-x-y);
t.add(w,make_pair(x+y,i));
} // 树状数组求距离最短点
}
}
return mst(e);
}
}
int main(){
ios::sync_with_stdio(false);
int n; cin>>n;
vector<pii> a(n);
for(auto &[x,y]:a)cin>>x>>y;
auto [w,e]=IAOI_lib::manhattan_mst(a);
cout<<w<<endl;
for(auto [u,v]:e)cout<<u+1<<' '<<v+1<<'\n';
return 0;
}
相关推荐
评论
共 1 条评论,欢迎与作者交流。
正在加载评论...