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;
}
相关推荐
MATLAB代码顾问16 分钟前
Python实现蜂群算法优化TSP问题
开发语言·python·算法
代码飞天21 分钟前
机器学习算法和函数整理——助力快速查阅
人工智能·算法·机器学习
jiushiapwojdap29 分钟前
LU分解法求解线性方程组Matlab实现
数据结构·其他·算法·matlab
笨笨饿43 分钟前
69_如何给自己手搓一个串口
linux·c语言·网络·单片机·嵌入式硬件·算法·个人开发
纽扣6671 小时前
【算法进阶之路】链表进阶:删除、合并、回文与排序全解析
数据结构·算法·链表
消失的旧时光-19432 小时前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法
智者知已应修善业2 小时前
【51单片机不用数组动态数码管显示字符和LED流水灯】2023-10-3
c++·经验分享·笔记·算法·51单片机
AI进化营-智能译站3 小时前
ROS2 C++开发系列16-智能指针管理传感器句柄|告别ROS2节点内存泄漏与野指针
java·c++·算法·ai
CS创新实验室3 小时前
从盘边到芯端——硬盘接口七十年变迁史
算法·磁盘调度
xvhao20134 小时前
单源、多源最短路
数据结构·c++·算法·深度优先·动态规划·图论·图搜索算法