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;
}
相关推荐
靠近彗星5 小时前
3.1 栈
数据结构·算法
sulikey5 小时前
一文彻底理解:如何判断单链表是否成环(含原理推导与环入口推算)
c++·算法·leetcode·链表·floyd·快慢指针·floyd判圈算法
Swift社区5 小时前
LeetCode 402 - 移掉 K 位数字
算法·leetcode·职场和发展
_码力全开_6 小时前
P1005 [NOIP 2007 提高组] 矩阵取数游戏
java·c语言·c++·python·算法·矩阵·go
墨染点香6 小时前
LeetCode 刷题【124. 二叉树中的最大路径和、125. 验证回文串】
算法·leetcode·职场和发展
Camel卡蒙6 小时前
红黑树详细介绍(五大规则、保持平衡操作、Java实现)
java·开发语言·算法
AhriProGramming6 小时前
Flask-SQLAlchemy精读-双语精选文章
python·算法·flask
孤廖6 小时前
吃透 C++ 栈和队列:stack/queue/priority_queue 用法 + 模拟 + STL 标准实现对比
java·开发语言·数据结构·c++·人工智能·深度学习·算法