1. 程式人生 > 實用技巧 >ZJNU 2427 - Distinct Distances (幾何、列舉)

ZJNU 2427 - Distinct Distances (幾何、列舉)


2017-2018 ACM-ICPC Pacific Northwest Regional Contest (Div.1) F - Distinct Distances


題意

在二維平面內給定\(n(1\leq n\leq 40)\)不同的

對於平面內某個點\(q\),其與給定的\(n\)個點兩兩之間的距離構成的集合為

\[\{|q−p_1|,|q−p_2|,\dots,|q−p_n|\} \]

問如何選出點\(q\)才能求出這個集合元素個數的最小值,輸出這個最小值




思路

可以發現,當\(n=1\)時答案一定為\(1\)

\(n=2\)時,可以取兩點連線的中垂線上任意一點,使得所有點到點\(q\)

距離相等

\(n=3\)時,由於三個不共線的點可確定一個圓,所以當三點不共線時,\(q\)可以是這個圓的圓心,答案為\(1\);當三點共線,只能取某兩個點中垂線上任意點,答案為\(2\)

\(n\gt 3\)時,可以發現

  • 選擇任意兩點的中垂線上一點,總能對答案做出至少為\(1\)的貢獻,總能保證答案\(\leq n-1\)

  • 選擇任意不共線三點(如果存在)組成的圓的圓心,總能對答案做出至少為\(2\)的貢獻,總能保證答案\(n\leq 2\)

  • 選擇任意四點,組成兩條線,在保證兩條線不平行的前提下,選取兩條線的兩條垂線的交點作為點\(q\),總能對答案做出至少為\(3\)的貢獻,總能保證答案\(\leq n-3\)

由於“任意不共線三點組成的圓的圓心”可以由“任意四點組成兩條線,其中兩條線公用一點”推得,所以可以不用繼續分類考慮

故選擇的點\(q\)只可能是某四個點組成的兩條不平行的線的中垂線的交點或者某兩個點的中垂線上的點

對於後者,我們直接取中點進行計算即可


直接暴力枚舉出點\(q\),對於集合內元素個數的計算,遍歷一遍各個點求出\(n\)個距離,排序後稍微處理下即可

列舉加上遍歷並排序,最後總時間複雜度為\(O(n^5logn)\)

(判斷平行與獲取中垂線的方法沒板子也可以直接推一下)




程式

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-10;

struct point
{
    double x,y;
    point(){}
    point(double x,double y):x(x),y(y){}
}p[45];
struct line
{
    point a,b;
    line(){}
    line(point a,point b):a(a),b(b){}
};

inline double Hypot(double x,double y) //直角三角形計算斜邊,建議重寫否則可能會TLE
{
    return sqrt(x*x+y*y);
}
inline double getPointDis(point a,point b) //兩點間距離
{
    return Hypot(a.x-b.x,a.y-b.y);
}

bool isParallel(line la,line lb) //判斷直線平行
{
    point &u1=la.a,&u2=la.b,&v1=lb.a,&v2=lb.b;
    if(u1.y==u2.y||v1.y==v2.y)
        return u1.y==u2.y&&v1.y==v2.y;
    return fabs((u1.x-u2.x)/(u1.y-u2.y)-(v1.x-v2.x)/(v1.y-v2.y))<eps;
}
line getPlumbLine(point a,point b) //獲取中垂線
{
    point p=point((a.x+b.x)/2.0,(a.y+b.y)/2.0);
    if(fabs(a.x-b.x)<eps)
        return line(p,point(p.x+1,p.y));
    if(fabs(a.y-b.y)<eps)
        return line(p,point(p.x,p.y+1));
    double k=-1.0/((a.y-b.y)/(a.x-b.x));
    return line(p,point(p.x+100,p.y+100.0*k));
}
point intersection(line la,line lb) //求兩直線交點
{
    point &u1=la.a,&u2=la.b,&v1=lb.a,&v2=lb.b;
    double t=((u1.x-v1.x)*(v1.y-v2.y)-(u1.y-v1.y)*(v1.x-v2.x))
            /((u1.x-u2.x)*(v1.y-v2.y)-(u1.y-u2.y)*(v1.x-v2.x));
    return point(u1.x+(u2.x-u1.x)*t,u1.y+(u2.y-u1.y)*t);
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lf%lf",&p[i].x,&p[i].y);
    
    if(n<3)
    {
        puts("1");
        return 0;
    }
    else if(n==3)
    {
        if(isParallel(line(p[1],p[2]),line(p[1],p[3]))) //三點共線
            puts("2");
        else
            puts("1");
        return 0;
    }
    
    int ans=n;
    double tmp[45];
    for(int a=1;a<=n;a++) for(int b=a+1;b<=n;b++)
        for(int c=1;c<=n;c++) for(int d=c+1;d<=n;d++)
        {
            if(isParallel(line(p[a],p[b]),line(p[c],p[d]))) //兩線平行直接跳過
                continue;
            point pt=intersection(getPlumbLine(p[a],p[b]),getPlumbLine(p[c],p[d])); //獲取中垂線交點
            for(int i=1;i<=n;i++)
                tmp[i]=getPointDis(pt,p[i]); //將所有距離求出
            sort(tmp+1,tmp+1+n); //排序
            int ansd=1;
            for(int i=2;i<=n;i++)
                if(tmp[i]-tmp[i-1]>=eps) //如果相鄰兩者差值大於eps,則算作兩種
                    ansd++;
            ans=min(ans,ansd);
        }
    for(int a=1;a<=n;a++) for(int b=a+1;b<=n;b++)
    {
        point pt=point((p[a].x+p[b].x)/2.0,(p[a].y+p[b].y)/2.0);
        for(int i=1;i<=n;i++)
            tmp[i]=getPointDis(pt,p[i]);
        sort(tmp+1,tmp+1+n);
        
        int ansd=1;
        for(int i=2;i<=n;i++)
            if(tmp[i]-tmp[i-1]>=eps)
                ansd++;
        ans=min(ans,ansd);
    }
    printf("%d\n",ans);
    
    return 0;
}