哈密顿回路来历:
天文学家哈密顿(William Rowan Hamilton) 提出,在一个有多个城市的地图网络中,寻找一条从 给定的起点 到 给定的终点 沿途恰好经过所有其他城市一次的路径。
这个问题和著名的七桥问题的不同之处在于,七桥问题过桥只需要确定起点,而不用确定终点。
哈密顿回路问题已被证明是NP问题,具有这样性质的问题,很难找到一个有效的算法。实际上对于某些顶点数不到100的网络,利用现有最好的算法和计算机也需要比较荒唐的时间(比如几百年)才能确定其是否存在一条这样的路径。
今天,我们就来看看这类问题是什么样的呢?(“这类问题”说明不一定只是问你是否存在这样一条路径)
由于这是个NP问题,因此我目前的菜鸡水平只能做简化版的。
luogu1523.旅行商简化版
题目背景
欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。
为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点(a,b)之间的相互到达的代价dist(a,b)=dist(b,a)且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再单项返回最西端,并且是一个哈密尔顿回路。
题目描述
著名的NPC难题的简化版本
现在笛卡尔平面上有n(n<=1000)个点,每个点的坐标为(x,y)(-2^31<x,y<2^31,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonic tour。
输入输出格式
输入格式:
第一行一个整数n
接下来n行,每行两个整数x,y,表示某个点的坐标。
输入中保证没有重复的两点,
保证最西端和最东端都只有一个点。
输出格式:
一行,即最短回路的长度,保留2位小数。
输入输出样例
输入样例#1:
7
0 6
1 0
2 3
5 4
6 1
7 5
8 2
输出样例#1:
25.58
这就是哈密顿问题的简化版。它把所有点放在了坐标轴上,并且规定了必须从最左端到最右端再回到最多段(就是规定了方向)。
由于题目中没有给每个点的坐标排序,我们可以先以横坐标x为第一关键字,纵坐标y为第二关键字从小到大排序。
这些点排好序之后,由于规定了方向,问题就转化成了求2条从最左上端(第一个点)到最右下端(最后一个点)的不相交的路径,且这两条路径经过所有点。把这两条路径都看做向右走的即可。
由于规定了方向,如果两条路径分别到达了第i、j个点,那么说明1到max(i,j)号点一定都经过了(因为只能向右走而不能折返,为了经过所有的点,左边的点必须都经过了才能到达本列)。
总结做法:
f[i][j]表示第一条路径走到第i个点,第二条路径走到第j个点的最短路径,要求i<j(这样max(i,j)就是j,能保证第二条路径一定走在前面),且第1到j个的点都被经过了。这样很容易想到,第j+1个点不是被第一个人走,就是被第二个人走,所以有转移方程
$f[i][j+1] = min{ f[i][j+1], f[i][j]+dis[j][j+1] }$
$f[j][j+1] = min{ f[i][j+1], f[i][j]+dis[i][j+1] }$
第一个方程是让第二条路径走到第j+1个点,没啥问题。
第二个方程是让第一条路径走到第j+1个点,但不好理解,且这么走后是不是违反了 第一条路径到达的点i < 第二条路径到达的点j 的原则?但我没有明确指定哪条路径是第一条,哪条路径是第二条呀!因此当第一条路径跑到前面(即第j+1个点)时,我们可以把两条路径“交换”一下,而这样做不会影响状态,相当于第一条路径成了第二条路径,走到了第j个点;而第二条路径成了第一条路径,走到了第j+1个点。如此即可确保第二条路径仍在第一条路径前面了!
1 #include<bits/stdc++.h> 2 #define maxn 1001 3 #define inf 1e30 4 using namespace std; 5 struct data 6 { 7 double x,y; 8 }g[maxn]; 9 double d[maxn][maxn],f[maxn][maxn]; 10 int i,j,k,n; 11 bool cmp(const data &a,const data &b) 12 { 13 if(a.x==b.x) return a.y<b.y; 14 return a.x<b.x; 15 } 16 double mdis(const data &a, const data &b) 17 { 18 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); 19 } 20 int main() 21 { 22 scanf("%d",&n); 23 for(i=1;i<=n;++i) scanf("%lf%lf",&g[i].x,&g[i].y); 24 sort(g+1,g+n+1,cmp);//按横坐标x从小到大排序 25 for(i=1;i<=n;++i) 26 for(j=i+1;j<=n;++j) 27 { 28 d[i][j]=mdis(g[i],g[j]); //求任意两点的距离 29 f[i][j]=inf; 30 } 31 f[1][2]=d[1][2]; 32 for(i=1;i<n;++i) 33 for(j=i+1;j<=n;++j) 34 { 35 f[i][j+1]=min(f[i][j+1],f[i][j]+d[j][j+1]); 36 f[j][j+1]=min(f[j][j+1],f[i][j]+d[i][j+1]); 37 } 38 printf("%.2lf ",f[n-1][n]+d[n-1][n]); 39 return 0; 40 }
旅行商问题
题目描述
给定一个n个顶点组成的带权有向图的距离矩阵d(i,j)(INF表示没有边)。要求从顶点0出发,经过每个顶点恰好一次后再回到顶点0.问所经过的边的总权重的最小值是多少?
输入样例#1:
7
0 6
1 0
2 3
5 4
6 1
7 5
8 2
输出样例#1:
25.58