题解
Boruvka算法:每个点都先找到离自己最近/最远的点,把这些边连上,再把形成的连通块看成点继续操作
显然这样做每次连通块数目至少会减半
问题就在于怎样快速求一个块到块外点的距离
我们可以把绝对值展开出4中情况
每种情况中,对于每一个点,把它对绝对值之和的贡献放到set里面(开4个set,存同一个点的4种贡献)
然后求一个块的最远点时,就把该连通块所有点的所有贡献从4个set里面全部删掉
查询就分4类讨论,每次都要找与自己相反的坐标贡献(因为绝对值的4种情况中,两个点的横纵坐标总是相减)
求完了之后再把点加回set
启发式合并,就是指把size小的合并到size大了里面,这样,下一次的合并代价至少会*2,空间开销与耗时会更小
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 100005
#define mp(x,y) make_pair(x,y)
int a[N][2],b[N][4];
set<pair<int,int> > S[4];
set<pair<int,int> >::iterator it;
int tmp[N][3],cnt,fa[N];
bool vis[N];
vector<int> id[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int main()
{
freopen("mst.in","r",stdin);
freopen("mst.out","w",stdout);
int n,i,j,k,u,ff,p,q;
n=gi();
for(i=1;i<=n;i++){
a[i][0]=gi();a[i][1]=gi();
fa[i]=i;id[i].push_back(i);
b[i][0]=a[i][0]+a[i][1];S[0].insert(mp(b[i][0],i));
b[i][1]=a[i][1]-a[i][0];S[1].insert(mp(b[i][1],i));
b[i][2]=a[i][0]-a[i][1];S[2].insert(mp(b[i][2],i));
b[i][3]=-a[i][0]-a[i][1];S[3].insert(mp(b[i][3],i));
}
long long ans=0;
while(1){
memset(vis,0,sizeof(vis));
cnt=0;
for(i=1;i<=n;i++){
ff=find(i);
if(id[ff].size()==n){
printf("%lld",ans);
return 0;
}
if(vis[ff])continue;
vis[ff]=1;
for(j=0;j<id[ff].size();j++)
for(k=0,u=id[ff][j];k<=3;k++)
S[k].erase(mp(b[u][k],u));
int mx=-1,pos=0;
for(j=0;j<id[ff].size();j++)
for(k=0,u=id[ff][j];k<=3;k++){
it=S[k^3].end();it--;
if((it->first+b[u][k])>mx){
mx=it->first+b[u][k];
pos=it->second;
}
}
for(j=0;j<id[ff].size();j++)
for(k=0,u=id[ff][j];k<=3;k++)
S[k].insert(mp(b[u][k],u));
tmp[++cnt][0]=mx;tmp[cnt][1]=ff;tmp[cnt][2]=pos;
}
for(i=1;i<=cnt;i++){
p=find(tmp[i][1]);
q=find(tmp[i][2]);
if(p==q)continue;
if(id[p].size()<id[q].size())
swap(p,q);
for(j=0;j<id[q].size();j++)
id[p].push_back(id[q][j]);
fa[q]=p;ans+=tmp[i][0];
//printf("%d\n",tmp[i][0]);
}
}
}