洛谷 P1429 平面最近點對(加強版)分治/暴力+二分

https://www.luogu.com.cn/problem/P1429
在這裏插入圖片描述

思路一:正經解法:分治。首先把nn個點按照xx排序,每次按照p[mid].xp[mid].x把點集分成兩部分,solve(l,mid)solve(mid+1,r)solve(l,mid)、solve(mid+1,r)得到每一部分點對之間的最小值ansans。那麼總體最小值要麼等於ansans,要麼等於左右兩部分各選一個點組成的點對之間的距離。現在考慮後半部分怎麼算,暴力枚舉肯定是不行的,我們可以利用已知信息——ansans來入手,顯然可以把需要考慮的點的xx的範圍限定到[p[mid].xans,p[mid].x+ans][p[mid].x-ans,p[mid].x+ans],此時畫圖可以知道對於任意一個位於該範圍內的點ii,滿足p[j].yp[i].y<ansp[j].y-p[i].y<ans的點jj的數量不超過66個,那麼只要我們把這個範圍內的點按照yy排序後,就可以在O(n)O(n)內通過枚舉得到結果。但是這樣複雜度是O(nlg2n)O(nlg^2n)的,還可以再優化嗎?可以,按照yy排序的過程沒必要利用sortsort來實現,我們這個過程本來就是遞歸求解的,所以可以直接利用歸併排序的MergeMerge過程實現排序,這樣複雜度就降到O(nlgn)O(nlgn)了,同時爲了減少sqrtsqrt運算,代碼中使用的是距離的平方進行比較,看代碼的時候要注意一下~。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
using ll=long long;

const int maxn=2e5+5;

struct point
{
    ll x,y;
    point(){}
    point(ll x,ll y):x(x),y(y){}
    bool operator <(const point &a)const
    {
        if(x==a.x)
            return y<a.y;
        return x<a.x;
    }
}p[maxn],tmp[maxn];

int n;
int idx[maxn];

inline ll dis(point a,point b)
{
    return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}

inline void Merge(int l,int mid,int r)//歸併排序
{
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)
    {
        if(p[i].y<=p[j].y)//按照y
            tmp[k++]=p[i++];
        else
            tmp[k++]=p[j++];
    }
    while(i<=mid)
        tmp[k++]=p[i++];
    while(j<=r)
        tmp[k++]=p[j++];
    for(int i=l;i<=r;i++)
        p[i]=tmp[i];
}

ll solve(int l,int r)
{
    if(l==r)//一個點
        return 2e18; //返回一個無意義的極限值
    if(l+1==r)//兩個點
    {
        if(p[l].y>p[r].y)//別忘了這一步
            swap(p[l],p[r]);
        return dis(p[l],p[r]);
    }
    int mid=(l+r)>>1;
    ll base=p[mid].x; //記錄基準值
    ll ans=solve(l,mid);
    ans=min(ans,solve(mid+1,r));
    Merge(l,mid,r); //按y進行歸併排序
    int cnt=0;
    ll v;
    for(int i=l;i<=r;i++)
    {
        v=abs(base-p[i].x);
        v*=v;
        if(v<ans)
            idx[++cnt]=i; //計算出位於所限制區間內的點
    }
    for(int i=1;i<=cnt;i++)
    {
        for(int j=i+1;j<=cnt;j++)
        {
            v=abs(p[idx[j]].y-p[idx[i]].y);
            v*=v;
            if(v>=ans)
                break;  //只計算縱座標之差的絕對值小於預定值 的點對
            ans=min(ans,dis(p[idx[i]],p[idx[j]]));
        }
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%lld %lld",&p[i].x,&p[i].y);
    sort(p,p+n);
    printf("%.4f\n",sqrt(solve(0,n-1)));
    return 0;
}

思路二:暴力+二分剪枝。首先還是要對nn個點按照xx排序,然後枚舉每一個點,因爲已知距離最小值爲ansans,所以在枚舉第ii個點的時候,可以把xx限制在[p[i].xans,p[i].x+ans][p[i].x-ans,p[i].x+ans]之內,因爲點集按照xx是有序的,所以我們可以通過二分找到這個限制所對應的區間[l,r][l,r],然後計算ii[l,r][l,r]之間的點對的距離。這個複雜度就比較玄學了,不過還是可以AA掉這道題的,而且代碼非常好寫。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
using ll=long long;

const int maxn=2e5+5;

struct point
{
    ll x,y;
    point(){}
    point(ll x,ll y):x(x),y(y){}
    bool operator <(const point &a)const
    {
        if(x==a.x)
            return y<a.y;
        return x<a.x;
    }
}p[maxn];

int n;

inline double dis(point a,point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%lld %lld",&p[i].x,&p[i].y);
    sort(p,p+n);
    double ans=2e9;
    int l,r;
    point base(0,0);
    for(int i=0;i<n;i++)
    {
        base.x=p[i].x-ans-1;
        l=lower_bound(p,p+n,base)-p;
        base.x=p[i].x+ans+1;
        r=upper_bound(p,p+n,base)-p-1;
        while(l<=r)
        {
            if(i==l)
                ++l;
            else
                ans=min(ans,dis(p[i],p[l++]));
        }
    }
    printf("%.4f\n",ans);
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章