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;
}
相关推荐
梁辰兴38 分钟前
数据结构:排序
数据结构·算法·排序算法·c·插入排序·排序·交换排序
野犬寒鸦1 小时前
力扣hot100:搜索二维矩阵 II(常见误区与高效解法详解)(240)
java·数据结构·算法·leetcode·面试
菜鸟得菜1 小时前
leecode kadane算法 解决数组中子数组的最大和,以及环形数组连续子数组的最大和问题
数据结构·算法·leetcode
楼田莉子2 小时前
C++算法专题学习——分治
数据结构·c++·学习·算法·leetcode·排序算法
一支鱼2 小时前
leetcode常用解题方案总结
前端·算法·leetcode
ulias2122 小时前
各种背包问题简述
数据结构·c++·算法·动态规划
m0_570466412 小时前
代码随想录算法训练营第二十八天 | 买卖股票的最佳实际、跳跃游戏、K次取反后最大化的数组和
java·开发语言·算法
吃着火锅x唱着歌3 小时前
LeetCode 1537.最大得分
算法·leetcode·职场和发展
数模加油站3 小时前
25高教社杯数模国赛【C题超高质量思路+可运行代码】第十弹
算法·数学建模·数模国赛·高教社杯全国大学生数学建模竞赛
ulias2123 小时前
动态规划入门:从记忆化搜索到动态规划
算法·动态规划