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;
}
相关推荐
jiao_mrswang42 分钟前
leetcode-18-四数之和
算法·leetcode·职场和发展
qystca1 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
薯条不要番茄酱1 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
今天吃饺子1 小时前
2024年SCI一区最新改进优化算法——四参数自适应生长优化器,MATLAB代码免费获取...
开发语言·算法·matlab
是阿建吖!1 小时前
【优选算法】二分查找
c++·算法
王燕龙(大卫)1 小时前
leetcode 数组中第k个最大元素
算法·leetcode
不去幼儿园2 小时前
【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参
人工智能·python·算法·机器学习·强化学习
Mr_Xuhhh2 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
盼海3 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
网易独家音乐人Mike Zhou6 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot