Codeforces-Gym 104849J:Traveling Salesperson in an Island(计算几何+最短路)

Problem J. Traveling Salesperson in an Island

Time Limit: 2 seconds

You are a salesperson at one of the ports in an island. You have to visit all the ports of the island and then come back to the starting port. Because you cannot swim and are scared of the sea, you have to stay on the land during your journey.

The island is modeled as a polygon on a two-dimensional plane. The polygon is simple, that is, its vertices are distinct and no two edges intersect or touch, other than consecutive edges which touch at their common vertex. In addition, no two consecutive edges are collinear. Each port in the island is modeled as a point on the boundary of the polygon. Your route is modeled as a closed curve that does not go outside of the polygon.

In preparation for the journey, you would like to compute the minimum length of a route to visit all the ports and return to the starting port.

Input

The input consists of a single test case of the following format.
n m x 1 y 1 . . . x n y n x 1 ′ y 1 ′ . . . x m ′ y m ′ n\ m\\ x_1\ y_1\\ .\\ .\\ .\\ x_n\ y_n\\ x^′_1\ y^′_1\\ .\\ .\\ .\\ x^′_m\ y^′_m n mx1 y1...xn ynx1′ y1′...xm′ ym′

The first line contains two integers n n n and m m m, which satisfy 3 ≤ n ≤ 100 3 \le n \le 100 3≤n≤100 and 2 ≤ m ≤ 100 2 \le m \le 100 2≤m≤100.

Here, n n n is the number of vertices of the polygon modeling the island, and m m m is the number of the ports in the island. Each of the next n n n lines consists of two integers x i x_i xi and y i y_i yi, which are the coordinates of the i i i-th vertex of the polygon, where 0 ≤ x i ≤ 1000 0 \le x_i \le 1000 0≤xi≤1000 and 0 ≤ y i ≤ 1000 0 \le y_i \le 1000 0≤yi≤1000.The vertices of the polygon are given in counterclockwise order.

Each of the m following lines consists of two integers x j ′ x^′_j xj′ and y j ′ y^′_j yj′, which are the coordinates of the j j j-th port. The route starts and ends at ( x 1 ′ , y 1 ′ ) (x^′_1, y^′_1) (x1′,y1′). It is guaranteed that all the ports are on the boundary of the polygon and pairwise distinct.

Output

Output in a line the minimum length of a route to visit all the ports and return to the starting port. The relative error of the output must be within 1 0 − 6 10^{−6} 10−6.

Sample Input 1

4 4

0 0

2 0

2 2

0 2

1 0

1 2

2 1

0 1

Sample Output 1

5.656854249492381

Sample Input 2

8 2

0 0

4 0

4 4

0 4

0 3

3 3

3 1

0 1

0 0

0 4

Sample Output 2

16.64911064067352

These samples are depicted in the following figures. The shortest routes are depicted by the thick lines. The gray polygons represent the islands.

The small disks represent the ports in the islands. Note that the route does not have to be simple, i.e., the route may intersect or overlap itself as in the second sample, in which the same path between the two ports is used twice.

思路:因为所有点都在多边形上,所以比较容易想到按照顺时针或逆时针依次访问所有port所走的路径是最短的。我们把port加到多边形上,得出访问顺序后,接下来就是如何求出这个最短路径了。

考虑任意2点之间是否能建一条边,判断这2点构成的线段是否完全在多边形内部,若在多边形内部则可以建一条边。

如何判断一条线段是否在简单多边形内部呢?

  1. 线段和多边形的所有边都不内交。

    如果线段和多边形的某条边内交(两线段内交是指两线段相交且交点不在两线段的端点),因为多边形的边的左右两侧分属多边形内外2个部分,所以线段一定会有一部分在多边形外。

  2. 线段和多边形交于线段的两端点并不会影响线段是否在多边形内;但是如果多边形的某个顶点和线段相交,还必须判断两相邻交点之间的线段是否包含于多边形内部,如下图。

    因此我们可以先求出所有和线段相交的多边形的顶点,然后按照 ( x , y ) (x,y) (x,y)坐标排序,这样相邻的两个点就是在线段上相邻的两交点,如果任意相邻两点的中点也在多边形内,则该线段一定在多边形内。

    判断点是否在多边形内是个经典问题,网上有很多解决方法,这里就不赘述了。

建完边之后,就可以用Flyod跑个最短路,然后按照访问顺序累计答案即可。复杂度 O ( ( n + m ) 3 ) 。 O\big((n+m)^3\big)。 O((n+m)3)。

cpp 复制代码
#include<bits/stdc++.h>
#define fi first
#define se second
#define lson (k<<1)
#define rson (k<<1)+1
#define mid ((l+r)/2)
#define sz(x) int(x.size())
#define pii pair<int,int>
#define bit bitset<100000>
using namespace std;
const int MAX=2e6+10;
const int MOD=1e9+7;
const int INF=INT_MAX;
const double PI=acos(-1.0);
typedef long long ll;
struct Point
{
	double x,y;
	void show(){cout<<"("<<x<<","<<y<<") ";}
	bool operator==(const Point&B)const{return x==B.x&&y==B.y;}
};
Point operator+(Point A,Point B){return (Point){A.x+B.x,A.y+B.y};}
Point operator-(Point A,Point B){return (Point){A.x-B.x,A.y-B.y};}
Point operator/(Point A,double B){return (Point){A.x/B,A.y/B};}
double operator*(Point A,Point B){return A.x*B.x+A.y*B.y;}
double operator^(Point A,Point B){return A.x*B.y-A.y*B.x;}
double cross(Point A,Point B){return A^B;}
double dis2(Point A,Point B){return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);}
double dis(Point A,Point B){return sqrt(dis2(A,B));}
int PointOnSegment(Point x,Point st,Point en){return ((st-x)*(en-x))<=0&&((st-x)^(en-x))==0;}
int SegmentIntersect(Point a1,Point a2,Point b1,Point b2)
{
	double c1=(a2-a1)^(b1-a1),c2=(a2-a1)^(b2-a1);
	double c3=(b2-b1)^(a1-b1),c4=(b2-b1)^(a2-b1);
	if(c1*c2>0||c3*c4>0)return 0;               //not intersect
	if(c1*c2<0&&c3*c4<0)return 1;               //normal intersect
	if((c1==0&&c2==0)||(c3==0&&c4==0))return -1;//coincide
	return 2;                                   //intersect on endpoint
}
int PointInPolygon(Point x,const vector<Point> &p)
{
    int wn=0,n=sz(p);
    for(int i=0;i<n;i++)
    {
        Point cur=p[i];
        Point nex=p[(i+1)%n];
        if(PointOnSegment(x,cur,nex))return -1;
        double k=(nex-cur)^(x-cur);
        double d1=cur.y-x.y;
        double d2=nex.y-x.y;
        if(k>0&&d1<=0&&d2>0)wn++;
        if(k<0&&d2<=0&&d1>0)wn--;
    }
    if(wn)return 1;
    return 0;
}
int SegmentInPolygon(const vector<Point> &v,Point a,Point b)
{
    vector<Point>its;
    int n=sz(v);
    for(int i=0;i<n;i++)
    {
        Point cur=v[i],nex=v[(i+1)%n];
        int state=SegmentIntersect(a,b,cur,nex);
        if(state==0)continue;
        if(state==1)return 0;
        if(PointOnSegment(cur,a,b))its.push_back(cur);
        if(PointOnSegment(nex,a,b))its.push_back(nex);
        if(PointOnSegment(a,cur,nex))its.push_back(a);
        if(PointOnSegment(b,cur,nex))its.push_back(b);
    }
    sort(its.begin(),its.end(),[](auto A,auto B){return A.x==B.x?A.y<B.y:A.x<B.x;});
    its.erase(unique(its.begin(),its.end()),its.end());
    for(int i=0;i<sz(its);i++)
    {
        Point cur=its[i];
        Point nex=its[(i+1)%sz(its)];
        if(PointInPolygon((cur+nex)/2,v)==0)return 0;
    }
    return 1;
}
double d[211][211];
int solve()
{
    int n,m;
    cin>>n>>m;
    vector<Point>p,q;
    p.resize(n);
    q.resize(m);
    for(int i=0;i<n;i++)scanf("%lf%lf",&p[i].x,&p[i].y);
    for(int i=0;i<m;i++)scanf("%lf%lf",&q[i].x,&q[i].y);
    vector<Point>s=q;
    s.insert(s.end(),p.begin(),p.end());
    vector<Point>v;
    for(int i=0;i<n;i++)
    {
        vector<Point>tmp;
        for(auto A:s)if(PointOnSegment(A,p[i],p[(i+1)%n]))tmp.push_back(A);
        erase_if(s,[=](auto A){return PointOnSegment(A,p[i],p[(i+1)%n]);});
        sort(tmp.begin(),tmp.end(),[=](auto A,auto B){return dis2(A,p[i])<dis2(B,p[i]);});
        v.insert(v.end(),tmp.begin(),tmp.end());
    }
    v.erase(unique(v.begin(),v.end()),v.end());

    for(int i=0;i<sz(v);i++)
    for(int j=i+1;j<sz(v);j++)
    {
        d[i][i]=0;
        if(!SegmentInPolygon(p,v[i],v[j]))d[i][j]=d[j][i]=1e12;
        else d[i][j]=d[j][i]=dis(v[i],v[j]);
    }
    for(int k=0;k<sz(v);k++)
    for(int i=0;i<sz(v);i++)
    for(int j=0;j<sz(v);j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);

    vector<int>pos;
    for(int i=0;i<m;i++)pos.push_back(find(v.begin(),v.end(),q[i])-v.begin());
    sort(pos.begin(),pos.end());
    double ans=0;
    for(int i=0;i<m;i++)ans+=d[pos[i]][pos[(i+1)%m]];
    return printf("%.20lf\n",ans);
}
int main()
{
    int T=1;
    //cin>>T;
    while(T--)solve();
    return 0;
}
相关推荐
XH华2 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生3 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_3 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子3 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡3 小时前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin3 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码4 小时前
Cmd命令大全(万字详细版)
python·算法·小程序
scan7244 小时前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活4 小时前
理解支持向量机
算法·机器学习·支持向量机
大山同学4 小时前
第三章线性判别函数(二)
线性代数·算法·机器学习