题目1 逆序对
题面
在⼀个数组 A [ 1.. n ] A[1..n] A[1..n]中,逆序对(inversion)是⼀对索引 ( i , j ) (i,j) (i,j),满⾜ i < j i<j i<j且 A [ i ] > A [ j ] A[i]>A[j] A[i]>A[j]。⼀个包含 n n n个元素的数组中的逆序对数量介于 0 0 0(如果数组已排序)和 ( n 2 ) \begin{pmatrix}n\\2\end{pmatrix} (n2)(如果数组完全逆序)之间。设计⼀个⾼效的算法计算数组 A [ 1.. n ] A[1..n] A[1..n]中逆序对的数量。给出算法的基本思路和伪代码描述,分析算法的时间复杂度。
基本思路
使用分治法,记当前子数组左端点坐标为 l l l,当前子数组右端点坐标为 r r r。记逆序对数总数为 r e s res res。初始时, l = 1 l=1 l=1, r = n r=n r=n。对于每个 A [ l . . r ] A[l..r] A[l..r],将子数组从 m = l + r 2 m=\frac{l+r}{2} m=2l+r处断开。这样,子数组就被划分成了 A [ l . . m ] A[l..m] A[l..m]和 A [ m + 1.. r ] A[m+1..r] A[m+1..r]
此时,逆序对就被分成了3类:
1.逆序的两个元素都在断开处左边
A
[
l
.
.
m
]
A[l..m]
A[l..m]内;
2.逆序的两个元素都在断开处右边
A
[
m
+
1..
r
]
A[m+1..r]
A[m+1..r]内;
3.逆序的两个元素分别在断开处左边和右边。
对于前两种情况,都可以直接递归地调用递归函数求解。
对于第三种情况,需要对数组进行归并排序,但是,还需要将排序完成的数字存入一个临时数组中,记临时数组为 T T T。在归并排序过程中,从左到右访问每个 i ∈ [ l , m ] i\in [l,m] i∈[l,m]和每个 j ∈ [ m + 1 , r ] j\in [m+1,r] j∈[m+1,r],每次访问到 A [ i ] A[i] A[i]和 A [ j ] A[j] A[j]时, A [ l . . i − 1 ] A[l..i-1] A[l..i−1]已经有序, A [ m + 1.. j − 1 ] A[m+1..j-1] A[m+1..j−1]也已经有序,都存入了临时数组 T [ 1.. i + j − m − 2 ] T[1..i+j-m-2] T[1..i+j−m−2]中。
在归并排序过程中,若当前 A [ i ] > A [ j ] A[i]>A[j] A[i]>A[j],因为 A [ i ] A[i] A[i]已经是 A [ i . . m ] A[i..m] A[i..m]中最小的数了,所以 A [ i . . m ] A[i..m] A[i..m]中的每个数都会比 A [ j ] A[j] A[j]大,会产生逆序对:第一个元素为 A [ i . . m ] A[i..m] A[i..m]中的任一元素,第二个元素为 A [ j ] A[j] A[j]。 A [ i . . m ] A[i..m] A[i..m]中共存在 m − i + 1 m-i+1 m−i+1个元素,所以也产生了 m − i + 1 m-i+1 m−i+1个逆序对。更新当前逆序对总数 r e s ← r e s + m − i + 1 res\leftarrow res+m-i+1 res←res+m−i+1
在计算完成 A [ l . . r ] A[l..r] A[l..r]中的逆序对数后,将临时数组 T [ 1.. r − l + 1 ] T[1..r-l+1] T[1..r−l+1]中的所有元素写回 A [ l . . r ] A[l..r] A[l..r]中,返回上一层递归。因此,该算法实际上就是在归并排序的过程中统计逆序对数量。
总结:利用分治算法,分别计算 A [ l . . r ] A[l..r] A[l..r]左、右两半边的逆序对数量后,对 A [ l . . r ] A[l..r] A[l..r]进行归并排序。在每次将左右两半边数组合并的同时,对左半边每个元素计算其对右半边元素产生的逆序对数量,将结果累加。
算法
代码
#include<iostream>
using namespace std;
const int N=1e6+10;
int q[N],cmp[N];
long long res=0;
void merge_sort(int l,int r){
if (l>=r)
return;
int mid=l+r>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r){
if (q[i]<=q[j]){
cmp[k++]=q[i++];
}
else{
res+=mid-i+1;
cmp[k++]=q[j++];
}
}
while(i<=mid)
cmp[k++]=q[i++];
while(j<=r)
cmp[k++]=q[j++];
for(int i=l,j=0;i<=r;)
q[i++]=cmp[j++];
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>q[i];
merge_sort(0,n-1);
cout<<res;
}
时间复杂度
时间复杂度:设完整执行整个算法需要时间 T ( n ) T(n) T(n),则处理左边的逆序元素和处理右边的逆序元素都需要 T ( n / 2 ) T(n/2) T(n/2)的时间。每次在将左右两半边的数组归并与计算逆序对时,需要的时间复杂度为 O ( n ) O(n) O(n)。因此,总时间复杂度为 T ( n ) = 2 T ( n / 2 ) + O ( n ) = O ( n log n ) T(n)=2T(n/2)+O(n)=O(n\log n) T(n)=2T(n/2)+O(n)=O(nlogn)
题目2 二维偏序
题面
给定⼆维平⾯上两个不同的点 p p p和 q q q,如果 p . x ≤ q . x p.x\le q.x p.x≤q.x且 p . y ≤ q . y p.y\le q.y p.y≤q.y,称 q q q⽀配 p p p。给定⼀个点集 P P P,设计⼀个⾼效的算法,计算每⼀个点 p ∈ P p\in P p∈P⽀配的点的数量。给出算法的基本思路和伪代码描述,分析算法的时间复杂度。
基本思路
记点 p ∈ P p\in P p∈P的横、纵坐标分别为 p . x p.x p.x和 p . y p.y p.y,记其支配的点的数量为 p . r e s p.res p.res,记点集中的元素数量 ∣ P ∣ = n |P|=n ∣P∣=n。虽然集合是无序的,但是为了方便输入和输出结果,给每个点都赋予一个编号值 p . i d p.id p.id。在后续的过程中, p . x , p . y , p . i d p.x,p.y,p.id p.x,p.y,p.id都不会变化。
首先,同样需要对数组排序。将每个点的横坐标作为第一关键字,将每个点的纵坐标作为第二关键字,对点集
P
P
P进行排序。记排序后有序的点序列为
A
[
1..
n
]
A[1..n]
A[1..n]。
初始时,
l
=
1
,
r
=
n
l=1,r=n
l=1,r=n,每一次,找到点序列
A
[
l
.
.
r
]
A[l..r]
A[l..r]的中位数
m
=
⌊
l
+
r
2
⌋
m=\lfloor \frac{l+r}{2}\rfloor
m=⌊2l+r⌋,将点序列分割为
A
[
l
.
.
m
]
A[l..m]
A[l..m]和
A
[
m
+
1..
r
]
A[m+1..r]
A[m+1..r]
此时,点对分为了3类:
1.两个点都在
A
[
l
.
.
m
]
A[l..m]
A[l..m]中。
2.两个点都在
A
[
m
+
1..
r
]
A[m+1..r]
A[m+1..r]中。
3.一个点在
A
[
l
.
.
m
]
A[l..m]
A[l..m]中,另一个点在
A
[
m
+
1..
r
]
A[m+1..r]
A[m+1..r]中。
对于前两种情况,都可以直接调用递归函数求解。对于第三种情况,利用归并排序的思想,以纵坐标为关键字再次对点序列重新排序。同样地,利用双指针算法,将排序完成的点存入一个临时点序列中,记这个临时点序列为 T T T。因为在分治算法开始前已经把所有点按照横坐标为第一关键词排过序了,所以对于 ∀ i ∈ [ l , m ] \forall i\in[l,m] ∀i∈[l,m]和 ∀ j ∈ [ m + 1 , r ] \forall j\in[m+1,r] ∀j∈[m+1,r],都满足: A [ i ] . x ≤ A [ j ] . x A[i].x\le A[j].x A[i].x≤A[j].x。在归并排序过程中,从左到右访问每个 i ∈ [ l , m ] i\in [l,m] i∈[l,m]和每个 j ∈ [ m + 1 , r ] j\in [m+1,r] j∈[m+1,r],每次访问到 A [ i ] A[i] A[i]和 A [ j ] A[j] A[j]时, A [ l . . i − 1 ] A[l..i-1] A[l..i−1]的纵坐标已经有序, A [ m + 1.. j − 1 ] A[m+1..j-1] A[m+1..j−1]的纵坐标也已经有序,都存入了临时点序列 T [ 1.. i + j − m − 2 ] T[1..i+j-m-2] T[1..i+j−m−2]中。
在归并排序过程中,若当前 A [ i ] . y > A [ j ] . y A[i].y\ > A[j].y A[i].y >A[j].y,因为 A [ i ] A[i] A[i]已经是 A [ l . . i ] A[l..i] A[l..i]中纵坐标最大的点了,而根据先前的排序结果可得 A [ i − 1 ] . y ≤ A [ j ] . y A[i-1].y\le A[j].y A[i−1].y≤A[j].y,所以 A [ l . . i − 1 ] A[l..i-1] A[l..i−1]中的每个点的纵坐标都会比 A [ j ] A[j] A[j]小。又因为在初始化时对横坐标进行了排序,根据上文,所以必定有: A [ i − 1 ] . x ≤ A [ j ] . x A[i-1].x\le A[j].x A[i−1].x≤A[j].x。所以,对 A [ j ] A[j] A[j]点满足: A [ l . . i − 1 ] A[l..i-1] A[l..i−1]中的所有点都被 A [ j ] A[j] A[j]支配。 A [ l . . i − 1 ] A[l..i-1] A[l..i−1]中共有 i − l i-l i−l个点。需要说明的是,若 i = l i=l i=l,则认为 A [ l . . l − 1 ] A[l..l-1] A[l..l−1]有0个点。若在归并右半边点序列时已经将左半边点序列排列完成,此时的 i = m + 1 i=m+1 i=m+1,仍然满足 A [ j ] A[j] A[j]支配了 A [ l . . m ] A[l..m] A[l..m]中的 m − l + 1 m-l+1 m−l+1个点,因此不需要对边界情况进行特判,直接按下述公式更新 A [ j ] A[j] A[j]点支配的点的数量: A [ j ] . r e s ← A [ j ] . r e s + i − 1 A[j].res\leftarrow A[j].res+i-1 A[j].res←A[j].res+i−1
在计算完成 A [ l . . r ] A[l..r] A[l..r]中支配点数后,将临时数组 T [ 1.. r − l + 1 ] T[1..r-l+1] T[1..r−l+1]中的所有元素写回 A [ l . . r ] A[l..r] A[l..r]中,返回上一层递归。因此,该算法实际上就是在归并排序的过程中统计每个点支配点的数量。
算法
需要说明的是,在实际处理中为了遍历数组,需要将每个点在数组中最终的位置与每个点在集合中初始的编号一一映射,在输入输出时需要额外处理。在伪代码中不体现这一点。
时间复杂度
在执行分治算法前需要先对点集进行排序,这一操作的时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)
在分治算法内部,设整个分治算法需要时间 T ( n ) T(n) T(n),则处理左半边点序列和右半边点序列都需要 T ( n / 2 ) T(n/2) T(n/2)的时间。每次在将左右两半边的点按纵坐标归并排序时,需要从左到右扫描一遍数组,需要 O ( n ) O(n) O(n)的时间。因此,分治算法内部的时间复杂度为 T ( n ) = 2 T ( n / 2 ) + O ( n ) = O ( n log n ) T(n)=2T(n/2)+O(n)=O(n\log n) T(n)=2T(n/2)+O(n)=O(nlogn)
因此,总的时间复杂度为 O ( n log n ) + O ( n log n ) = O ( n log n ) O(n\log n)+O(n\log n)=O(n\log n) O(nlogn)+O(nlogn)=O(nlogn)
题目3 寻找峰值
题面
你正在参加⼀档游戏节⽬。你会看到⼀排盒⼦,每个盒⼦⾥都包含⼀个任意且唯⼀的数字。你的⽬标是在尽可能少地打开盒⼦的情况下找到⼀个盒⼦,其数字⼤于其左边和右边的盒⼦中的数字。当然,除⾮它是第⼀个或最后⼀个盒⼦,在这种情况下,它只需要⼤于其相邻的那个盒⼦的数字即可。假设⼀共有 n n n个盒⼦,你的算法的时间复杂度应该优于 O ( n ) O(n) O(n)。给出算法的基本思路和伪代码描述,分析算法的时间复杂度,并给出正确性证明。
基本思路
记录 n n n个盒子组成的序列为 A [ 1.. n ] A[1..n] A[1..n],第 i i i个盒子中的数值为 A [ i ] A[i] A[i]。因为第一个盒子和最后一个盒子都是合法解,所以假定: A [ 0 ] = − ∞ , A [ n + 1 ] = − ∞ A[0]=-\infty,A[n+1]=-\infty A[0]=−∞,A[n+1]=−∞,对这一盒子序列扩增为 A [ 0.. n + 1 ] A[0..n+1] A[0..n+1]。这样,就不需要再对边界情况进行特判了。
思想上,是分治思想。但在算法实际实现时,使用了迭代的形式。初始时, l = 1 , r = n l=1,r=n l=1,r=n。
每次循环时,取 m = l + r 2 m=\frac{l+r}{2} m=2l+r。
若当前 A [ m ] < A [ m + 1 ] A[m]<A[m+1] A[m]<A[m+1],可分两种情况:
1.在
[
m
+
1
,
r
]
[m+1,r]
[m+1,r]范围内,
A
[
m
+
1..
r
]
A[m+1..r]
A[m+1..r]是单调的,满足
A
[
m
]
<
A
[
m
+
1
]
.
.
.
<
A
[
r
]
A[m]<A[m+1]...<A[r]
A[m]<A[m+1]...<A[r],则
A
[
r
]
A[r]
A[r]为解;
2.在
[
m
+
1
,
r
]
[m+1,r]
[m+1,r]范围内,
A
[
m
+
1..
r
]
A[m+1..r]
A[m+1..r]不是单调的,则对
i
i
i,在
m
+
1
≤
i
≤
r
m+1\le i\le r
m+1≤i≤r范围内,第一个满足
A
[
i
]
>
A
[
i
+
1
]
A[i]>A[i+1]
A[i]>A[i+1]的
i
i
i即为解。所以,在
[
m
+
1
,
r
]
[m+1,r]
[m+1,r]范围内,必定存在解。
无论对于哪种情况,都只需迭代更新
l
l
l的值,
l
←
m
+
1
l\leftarrow m+1
l←m+1
若当前 A [ m ] > A [ m + 1 ] A[m]>A[m+1] A[m]>A[m+1],可分两种情况:
1.在
[
l
,
m
]
[l,m]
[l,m]范围内,
A
[
l
.
.
m
]
A[l..m]
A[l..m]是单调的,满足
A
[
l
]
>
A
[
l
+
1
]
.
.
.
>
A
[
m
]
A[l]>A[l+1]...>A[m]
A[l]>A[l+1]...>A[m],则
A
[
l
]
A[l]
A[l]为解;
2.在
[
l
,
m
]
[l,m]
[l,m]范围内,
A
[
l
.
.
m
]
A[l..m]
A[l..m]不是单调的,则对
i
i
i,在
l
≤
i
≤
m
l\le i\le m
l≤i≤m范围内,第一个满足
A
[
i
]
<
A
[
i
+
1
]
A[i]<A[i+1]
A[i]<A[i+1]的
i
i
i即为解。所以,在
[
l
,
m
]
[l,m]
[l,m]范围内,必定存在解。
无论对于哪种情况,都只需迭代更新
r
r
r的值,
r
←
m
r\leftarrow m
r←m
通过这种迭代,最终找到 l = r l=r l=r,即为符合题设要求需要找到的盒子。
算法
时间复杂度
由于每次迭代时都会舍弃数组中一半的数,所以时间复杂度为
O
(
log
n
)
O(\log n)
O(logn)
T
(
n
)
=
2
T
(
n
/
2
)
+
1
T(n)=2T(n/2)+1
T(n)=2T(n/2)+1,也能解得
O
(
log
n
)
O(\log n)
O(logn)
正确性证明
当序列长度
r
−
l
+
1
=
1
r-l+1=1
r−l+1=1时,有且只有那一个盒子为解。
当序列长度
r
−
l
+
1
=
2
r-l+1=2
r−l+1=2时,选两个数中较大者为解。
当序列长度
r
−
l
+
1
≥
3
r-l+1\ge 3
r−l+1≥3时,对于
∀
i
∈
[
l
,
r
]
\forall i\in [l,r]
∀i∈[l,r],存在以下两种情况:
若当前
A
[
i
]
<
A
[
i
+
1
]
A[i]<A[i+1]
A[i]<A[i+1],可进一步分两种情况:
1.在
[
i
+
1
,
r
]
[i+1,r]
[i+1,r]范围内,
A
[
i
+
1..
r
]
A[i+1..r]
A[i+1..r]是单调的,满足
A
[
i
]
<
A
[
i
+
1
]
.
.
.
<
A
[
r
]
A[i]<A[i+1]...<A[r]
A[i]<A[i+1]...<A[r],则
A
[
r
]
A[r]
A[r]为解。若
r
=
n
r=n
r=n,由于题设中允许打开最后一个盒子,所以假定
A
[
n
+
1
]
=
−
∞
A[n+1]=-\infty
A[n+1]=−∞,此时满足
A
[
n
−
1
]
<
A
[
n
]
>
A
[
n
+
1
]
A[n-1]<A[n]>A[n+1]
A[n−1]<A[n]>A[n+1],此时解是存在的。若
r
≠
n
r\ne n
r=n,由于进入这一步迭代时已经使得
A
[
r
]
>
A
[
r
+
1
]
A[r]>A[r+1]
A[r]>A[r+1]被满足,所以
A
[
r
]
A[r]
A[r]为解。
2.在
[
i
+
1
,
r
]
[i+1,r]
[i+1,r]范围内,
A
[
i
+
1..
r
]
A[i+1..r]
A[i+1..r]不是单调的,则对
j
j
j,在
i
+
1
≤
j
≤
r
i+1\le j\le r
i+1≤j≤r范围内,存在不单调的点位
A
[
j
−
1
]
<
A
[
j
]
>
A
[
j
+
1
]
A[j-1]<A[j]>A[j+1]
A[j−1]<A[j]>A[j+1]的
j
j
j即为解。所以,在
[
i
+
1
,
r
]
[i+1,r]
[i+1,r]范围内,必定存在解。
若当前
A
[
i
]
>
A
[
i
+
1
]
A[i]>A[i+1]
A[i]>A[i+1],可进一步分两种情况:
1.在
[
l
,
i
]
[l,i]
[l,i]范围内,
A
[
l
.
.
i
]
A[l..i]
A[l..i]是单调的,满足
A
[
l
]
>
A
[
l
+
1
]
.
.
.
>
A
[
i
]
>
A
[
i
+
1
]
A[l]>A[l+1]...>A[i]>A[i+1]
A[l]>A[l+1]...>A[i]>A[i+1],则
A
[
l
]
A[l]
A[l]为解。若
l
=
1
l=1
l=1,由于题设中允许打开第一个盒子,所以假定
A
[
0
]
=
−
∞
A[0]=-\infty
A[0]=−∞,此时满足
A
[
0
]
<
A
[
1
]
>
A
[
2
]
A[0]<A[1]>A[2]
A[0]<A[1]>A[2],此时解是存在的。若
l
≠
0
l\ne 0
l=0,由于进入这一步迭代时已经使得
A
[
l
−
1
]
<
A
[
l
]
A[l-1]<A[l]
A[l−1]<A[l]被满足,所以
A
[
l
]
A[l]
A[l]为解。
2.在
[
l
,
i
]
[l,i]
[l,i]范围内,
A
[
l
.
.
i
]
A[l..i]
A[l..i]不是单调的,则对
j
j
j,在
l
≤
j
≤
i
l\le j\le i
l≤j≤i范围内,存在不单调的点位
A
[
j
−
1
]
<
A
[
j
]
>
A
[
j
+
1
]
A[j-1]<A[j]>A[j+1]
A[j−1]<A[j]>A[j+1]的
j
j
j即为解。所以,在
[
l
,
i
]
[l,i]
[l,i]范围内,必定存在解。
上文论证了对于 ∀ i ∈ [ l , r ] \forall i\in [l,r] ∀i∈[l,r]时算法的正确性,解的必定存在性。实际实现时, i i i不是 [ l , r ] [l,r] [l,r]范围内任取的,而是固定取 i = m = ⌊ l + r 2 ⌋ ∈ [ l , r ] i=m=\lfloor\frac{l+r}{2}\rfloor\in [l,r] i=m=⌊2l+r⌋∈[l,r],因此3.3给出的算法是正确的。
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int n = nums.size();
// 辅助函数,输入下标 i,返回一个二元组 (0/1, nums[i])
// 方便处理 nums[-1] 以及 nums[n] 的边界情况
auto get = [&](int i) -> pair<int, int> {
if (i == -1 || i == n) {
return {0, 0};
}
return {1, nums[i]};
};
int left = 0, right = n - 1, ans = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (get(mid - 1) < get(mid) && get(mid) > get(mid + 1)) {
ans = mid;
break;
}
if (get(mid) < get(mid + 1)) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return ans;
}
};
//作者:力扣官方题解
//链接:https://leetcode.cn/problems/find-peak-element/solutions/998152/xun-zhao-feng-zhi-by-leetcode-solution-96sj/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
题目4
给定代码
基于下⾯的代码框架实现求解最近点对的分治算法。在100、1000、10000、100000个点上测试你的代码,
将运⾏时间总结在提交的pdf⽂件中。⼀些代码注意事项如下:
- 代码中不要包含任何中⽂
- 不要使⽤<bits/stdc++.h>头⽂件
- 不要在main函数后放置任何代码
- 不要改变给定的函数原型
// Note:
// You are free to utilize any C++ standard library functions.
// Please ensure to include the necessary headers below.
// Avoid using <bits/stdc++.h> to prevent potential compilation errors that could result in a score of zero.
#include <iostream>
#include <complex>
#include <vector>
#include <utility>
using namespace std;
typedef long double LD;
typedef complex<LD> Point;
// You can add more functions here.
pair<Point, Point> closest_pair(vector<Point>& P)
{
// Insert your code here. You can add more functions but do not change the
definition of this function.
}
int main(int argc, const char * argv[]) {
// You can insert code here to test your function.
return 0;
}
// Please refrain from including any code beyond the main function,
// as any additional code will be removed during the code evaluation process.
代码
// Note:
// You are free to utilize any C++ standard library functions.
// Please ensure to include the necessary headers below.
// Avoid using <bits/stdc++.h> to prevent potential compilation errors that could result in a score of zero.
#include <iostream>
#include <complex>
#include <vector>
#include <utility>
#include <algorithm>
#include <iomanip>
using namespace std;
typedef long double LD;
typedef complex<LD> Point;
// You can add more functions here.
bool cmp_x(const Point& a, const Point& b) {
return a.real() < b.real() || (a.real() == b.real() && a.imag() < b.imag());
}
bool cmp_y(const Point& a, const Point& b) {
return a.imag() < b.imag();
}
int n;
const int MAXN = 100005;
double mindist;
pair<Point, Point> res;
vector<Point> a;
void upd_ans(const Point& a, const Point& b) {
double dist =
sqrt((a.real() - b.real()) * (a.real() - b.real()) + (a.imag() - b.imag()) * (a.imag() - b.imag()) + .0);
if (dist < mindist) {
mindist = dist;
res.first = a;
res.second = b;
}
}
void rec(int l, int r) {
if (r - l <= 3) {
for (int i = l; i <= r; ++i)
for (int j = i + 1; j <= r; ++j) upd_ans(a[i], a[j]);
sort(a.begin() + l, a.begin() + r + 1, &cmp_y);
return;
}
int m = (l + r) >> 1;
int midx = a[m].real();
rec(l, m), rec(m + 1, r);
inplace_merge(a.begin() + l, a.begin() + m + 1, a.begin() + r + 1, &cmp_y);
static Point t[MAXN];
int tsz = 0;
for (int i = l; i <= r; ++i)
if (abs(a[i].real() - midx) < mindist) {
for (int j = tsz - 1; j >= 0 && a[i].imag() - t[j].imag() < mindist; --j)
upd_ans(a[i], t[j]);
t[tsz++] = a[i];
}
}
pair<Point, Point> closest_pair(vector<Point>& P)
{
mindist = 1E20;
n = P.size();
a = P;
sort(a.begin(), a.end(), cmp_x);
rec(0, n - 1);
return res;
// Insert your code here. You can add more functions but do not change the definition of this function.
}
int main(int argc, const char* argv[]) {
cin>>n;
for(int i=0;i<n;i++){
double x,y;
cin>>x>>y;
a.push_back({x,y});
}
pair<Point,Point> res = closest_pair(a);
double dist = sqrt((res.first.real() - res.second.real()) * (res.first.real() - res.second.real()) + (res.first.imag() - res.second.imag()) * (res.first.imag() - res.second.imag()) + .0);
cout<<fixed<<setprecision(4)<<dist;
return 0;
}
// Please refrain from including any code beyond the main function,
// as any additional code will be removed during the code evaluation process.