解题报告-老逗找基友 (friends)
老逗找基友 (friends)
题目背景
吴老逗有 \(n\) 个基友,位于平面直角坐标系的整点上。每个基友已与其最近的基友(如有多个则取编号最小)建立了双向心灵感应。但这样形成的网络可能不连通,因此吴老逗可以使用爱之魔法(连接自己与某个基友)来使网络连通。目标是使用最少的魔法次数使网络连通,并最小化连通网络中任意两个基友之间的最大距离(这里距离指感应次数,即边数)。
题目描述
给定 \(n\) 个点 \((x_i, y_i)\),点 \(i\) 与点 \(j\) 的距离定义为曼哈顿距离 \(|x_i - x_j| + |y_i - y_j|\)。初始时,每个点都连接其最近点(多个最近点时取编号最小)。这样得到一个图(可能不连通)。现在可以添加若干条边(每条边连接吴老逗(编号0)和某个基友),要求添加最少的边使图连通,并求连通后图的直径(即任意两点间最短路径的最大值)的最小可能值。
输入输出格式
输入格式:
- 第一行:正整数 \(n\)
- 接下来 \(n\) 行:每行两个整数 \(x, y\)
输出格式:
- 一行:一个正整数,表示两个基友相隔感应次数的最小值的最大值(即最小可能直径)。
输入样例
5
1 1
2 2
4 4
4 5
2 3
输出样例:
4
数据规模
- 对于 \(30\)% 的数据,\(n \leq 2×10^3\)。
- 对于另外 \(30\)% 的数据,\(n \leq 10^5\),\(0 \leq x,y \leq 10^3\)。
- 对于 \(100\)% 的数据,\(n \leq 10^5\),\(x,y \leq 10^9\)。
解题报告
阴间的码农题和缝合怪。
题目分为两部分:找曼哈顿距离下的最近点对、计算树的直径。
先处理曼哈顿距离下的最近点对,这也是一个经典的问题了。
老样子,遇到绝对值,直接分讨打开。
假设查询的点为 \((x_0,y_0)\),目前存在的点为 \((x_i,y_i)\),分讨打开绝对值,只有以下情况:
- \(x_0 \geq x_i\) 且 \(y_0 \geq y_i\),\(|x_0-x_i|+|y_0-y_i|=(x_0+y_0)-(x_i+y_i)\),只需使符合条件的点(左下区域)的 \(x+y\) 最大;
- \(x_0 \geq x_i\) 且 \(y_0 \leq y_i\),\(|x_0-x_i|+|y_0-y_i|=(x_0-y_0)-(x_i-y_i)\),只需使符合条件的点(左上区域)的 \(x-y\) 最大;
- \(x_0 \leq x_i\) 且 \(y_0 \geq y_i\),\(|x_0-x_i|+|y_0-y_i|=(y_0-x_0)-(y_i-x_i)\),只需使符合条件的点(右下区域)的 \(y-x\) 最大;
- \(x_0 \leq x_i\) 且 \(y_0 \leq y_i\),\(|x_0-x_i|+|y_0-y_i|=(x_i+y_i)-(x_0+y_0)\),只需使符合条件的点(右上区域)的 \(x+y\) 最小;
不过,我们不需要专门对每种情况都处理,实际上,通过将坐标系翻转,我们可以把其他的情况统一到第一种情况。所以,接下来只对第一种情况(左下区域)进行分析。
显然,如果一个点 \(i\) 被选为最近点,它必然在查询之前出现,满足:\(x_i\leq x_0,y_i\leq y_0\),并且有 \(|x_0-x_i|+|y_0-y_i|=(x_0+y_0)-(x_i+y_i)\),其中 \((x_0+y_0)\) 为定值,那么就只需要 \(x_i+y_i\) 最大就可以了。
注意为了满足偏序条件,我们需要把每个点按 \(x\) 坐标升序,同时用树状数组维护 \(y\) 坐标,坐标变换后计算四次。
要注意:
- 按 \(x\) 坐标升序排序时,也要将 \(y\) 坐标作为第二关键字排序。
- 需要对变换前和后的每一个坐标都离散化。
显然,建出图后一定是一个森林。
然后因为要求使用最少的魔法次数使网络连通且连通后图的直径最小,显然直接将每棵树的直径的中心和节点 \(0\) 直接相连是最优的。
接下来就是两次广搜找出每一棵树 的直径,答案就是 $ \max( \dfrac { \text{最长直径+次长直径} }{2}+2,\text{最长直径} ) $。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=101100;
#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=min(x,y) )
inline int read()
{
int f=1,x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar(); }
return f*x;
}
// ---------基本数据--------------------------------------------------
struct point
{
int pos; // 编号
int x,y,X,Y; // 坐标
int px,py; // 坐标排名
}p[N];
int n,ans;
int NUM[N<<2],top; // 离散化数组
int Mx,My;
// ---------向量表示边-----------------------------------------------------------
vector<int> e[N];
inline void add_edge(int u,int v)
{
e[u].push_back(v);
e[v].push_back(u);
}
// ---------并查集---------------------------------------------------------------
int dad[N];
int GetSet(int x)
{
if(dad[x]==x) return x;
return dad[x]=GetSet(dad[x]);
}
inline void Merge(int u,int v)
{
u=GetSet(u),v=GetSet(v);
dad[v]=u;
}
// ---------建图------------------------------------------------------------------
#define lowbit(x) ( x&(-x) )
struct node
{
int p,x;
}T[N<<2]; // 树状数组维护 x+y 的最大值。
node tp[N<<2]; // 每个点的最近点。
// 结构体 node 的"max"
inline void ckmx(node &u,node v)
{
if(u.x<v.x)
return (void)( u=v );
if(u.x==v.x && v.p<u.p)
return (void)( u=v );
}
// 结构体 node 的"min"
inline void ckmn(node &u,node v)
{
if(u.x>v.x)
return (void)( u=v );
if(u.x==v.x && v.p<u.p)
return (void)( u=v );
}
// 树状数组维护最大值
inline void update(int x,int pos,int val)
{
node tmp=(node){ pos,val };
for(;x<=top;x+=lowbit(x))
ckmx(T[x],tmp);
}
// 查询最大值及其位置
inline node query(int x)
{
node ans=(node){ 0,0 };
for(;x>0;x-=lowbit(x))
ckmx(ans,T[x]);
return ans;
}
// 维护横坐标的偏序
bool rule(point u,point v)
{
if(u.px==v.px) return u.py<v.py;
return u.px<v.px;
}
// 用一次排序维护横坐标的偏序,用树状数组维护纵坐标的偏序
// 当 x<x0,y<y0 时,|x0-x|+|y0-y|=(x0+y0)-(x+y)
// 所以左下方的最近点的 x+y 一定最大
inline void solve()
{
sort(p+1,p+n+1,rule);
for(int i=1;i<=n;i++)
{
node pos=query(p[i].py);
if(pos.p)
{
pos.x=abs(p[i].x+p[i].y-pos.x);
node tmp=(node){ p[i].pos,pos.x };
ckmn(tp[p[i].pos],pos);
}
update(p[i].py,p[i].pos,p[i].x+p[i].y);
}
for(int i=1;i<=top;i++) T[i]=(node){ 0,0 };
}
inline void debug()
{
for(int i=1;i<=n;i++)
printf("%d %d\n",i,tp[i].p);
puts("");
}
// 建图的主函数,分讨打开绝对值,用坐标翻转统一处理
inline void BuildGraph()
{
for(int i=1;i<=n;i++)
{
NUM[++top]=p[i].x; NUM[++top]=Mx-p[i].x;
NUM[++top]=p[i].y; NUM[++top]=My-p[i].y;
tp[i].x=INF;
dad[i]=i;
}
sort(NUM+1,NUM+top+1);
top=unique(NUM+1,NUM+top+1)-NUM-1;
for(int i=1;i<=n;i++)
{
p[i].x=p[i].X,p[i].y=p[i].Y;
p[i].px=lower_bound(NUM+1,NUM+top+1,p[i].x)-NUM;
p[i].py=lower_bound(NUM+1,NUM+top+1,p[i].y)-NUM;
}
solve(); //debug();
for(int i=1;i<=n;i++)
{
p[i].x=Mx-p[i].X+1,p[i].y=p[i].Y;
p[i].px=lower_bound(NUM+1,NUM+top+1,p[i].x)-NUM;
p[i].py=lower_bound(NUM+1,NUM+top+1,p[i].y)-NUM;
}
solve(); //debug();
for(int i=1;i<=n;i++)
{
p[i].x=p[i].X,p[i].y=My-p[i].Y+1;
p[i].px=lower_bound(NUM+1,NUM+top+1,p[i].x)-NUM;
p[i].py=lower_bound(NUM+1,NUM+top+1,p[i].y)-NUM;
}
solve(); //debug();
for(int i=1;i<=n;i++)
{
p[i].x=Mx-p[i].X+1,p[i].y=My-p[i].Y+1;
p[i].px=lower_bound(NUM+1,NUM+top+1,p[i].x)-NUM;
p[i].py=lower_bound(NUM+1,NUM+top+1,p[i].y)-NUM;
}
solve(); //debug();
for(int i=1;i<=n;i++)
{
add_edge(i,tp[i].p);
Merge(i,tp[i].p);
}
}
//---------森林中每颗树的直径----------------------------------------------------
int dia[N],dis[N],ngr;
bool vis[N];
// 广搜找最远点
inline int bfs(int s)
{
memset(dis,-1,sizeof(dis));
queue<int> q; q.push(s);
int pos=s,mdis=dis[s]=0;
while(!q.empty())
{
int u=q.front(); q.pop();
if(dis[u]>mdis) mdis=dis[u],pos=u;
for(int i=0;i<e[u].size();i++)
{
int v=e[u][i];
if(dis[v]==-1)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return pos;
}
// 两遍广搜找到直径
inline void Getdia(int pos)
{
int s=bfs(pos);
int t=bfs(s);
dia[++ngr]=dis[t];
vis[GetSet(pos)]=true;
}
// 得到所有直径
inline void GetED()
{
for(int i=1;i<=n;i++)
if(!vis[GetSet(i)])
Getdia(i);
}
signed main()
{
freopen("friends.in","r",stdin);
freopen("friends.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
{
int x=read()+1,y=read()+1;
p[i]=(point){ i,x,y,x,y,0,0 };
ckmax(Mx,x),ckmax(My,y);
}
BuildGraph();
GetED();
sort(dia,dia+ngr+1);
ans=dia[ngr];
if(ngr>1) ckmax(ans,(dia[ngr]+1)/2+(dia[ngr-1]+1)/2+2);
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号