Mr. Kim has to deliver refrigerators to N customers. From the office, he is going to visit all the customers and then return to his home. Each location of the office, his home, and the customers is given in the form of integer coordinates (x,y) (0≤x≤100, 0≤y≤100) . The distance between two arbitrary locations (x1, y1) and (x2, y2) is computed by |x1-x2| + |y1-y2|, where |x| denotes the absolute value of x; for instance, |3|=|-3|=3. The locations of the office, his home, and the customers are all distinct. You should plan an optimal way to visit all the N customers and return to his among all the possibilities.
You are given the locations of the office, Mr. Kim’s home, and the customers; the number of the customers is in the range of 5 to 10. Write a program that, starting at the office, finds a (the) shortest path visiting all the customers and returning to his home. Your program only have to report the distance of a (the) shortest path.
You don’t have to solve this problem efficiently. You could find an answer by looking up all the possible ways. If you can look up all the possibilities well, you will get a perfect score.
[Constraints]
5≤N≤10. Each location (x,y) is in a bounded grid, 0≤x≤100, 0≤y≤100, and x, y are integers.
[Input]
You are given 10 test cases. Each test case consists of two lines; the first line has N, the number of the customers, and the following line enumerates the locations of the office, Mr. Kim’s home, and the customers in sequence. Each location consists of the coordinates (x,y), which is reprensented by ‘x y’.
[Output]
Output the 10 answers in 10 lines. Each line outputs the distance of a (the) shortest path. Each line looks like ‘#x answer’ where x is the index of a test case. ‘#x’ and ‘answer’ are separated by a space.
[I/O Example]
Input (20 lines in total. In the first test case, the locations of the office and the home are (0, 0) and (100, 100) respectively, and the locations of the customers are (70, 40), (30, 10), (10, 5), (90, 70), (50, 20).)
5 ← Starting test case #1 0 0 100 100 70 40 30 10 10 5 90 70 50 20 6 ← Starting test case #2 88 81 85 80 19 22 31 15 27 29 30 10 20 26 5 14 10 ← Starting test case #3 39 9 97 61 35 93 62 64 96 39 36 36 9 59 59 96 61 7 64 43 43 58 1 36 ... |
Output (10 lines in total)
#1 200 #2 304 #3 366 ... |
思路:经典题目,TSP旅行商问题(旅行问题,是从一个点出发,访问所有点之后,再回到出发点,每个点只能访问一次)。
①本题规定了出发点和终止点,且每个点也只能访问一次。
②第一行是告诉你有n个点,第二行先给出起始点和终止点的坐标,然后再给出n个点的坐标,这里的计算距离是曼哈顿距离。
③方法一:暴力N!,首先二层for循环,计算出两两之间的距离,然后对输入的n个点进行全排列。(源码及数据下载)
#include<stdio.h>
#include<iostream>
using namespace std;
int T,n;
int a[15][2];
int Map[15][15];
int sum;
void Swap(int s[],int i,int j){
//这样报错,很奇怪(若i和j相等时,则报错,指向同一个地址)
//s[i]=s[i]+s[j];
//s[j]=s[i]-s[j];
//s[i]=s[i]-s[j];
int x=s[i];
s[i]=s[j];
s[j]=x;
}
void operate(int s[],int i){
if(i>n) return;
if(i==n){
//printf("ac\n");
int ans=0;
int Start=0;
for(int i=1;i<=n;i++){
ans+=Map[Start][s[i]];
Start=s[i];
}
ans+=Map[Start][n+1];
if(ans<sum) sum=ans;
}else{
for(int j=i;j<=n;j++){
Swap(s,i,j);
operate(s,i+1);
Swap(s,j,i);
}
}
}
int main(){
//freopen("1.txt","r",stdin);
//freopen("2.txt","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d",&n);
//起始点
scanf("%d%d",&a[0][0],&a[0][1]);
//终止点
scanf("%d%d",&a[n+1][0],&a[n+1][1]);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i][0],&a[i][1]);
}
for(int i=0;i<n+2;i++){
for(int j=0;j<n+2;j++){
//两点之间:曼哈顿距离
int dx=(a[j][0]-a[i][0])>0?(a[j][0]-a[i][0]):(a[i][0]-a[j][0]);
int dy=(a[j][1]-a[i][1])>0?(a[j][1]-a[i][1]):(a[i][1]-a[j][1]);
Map[i][j]=Map[j][i]=dx+dy;
}
}
int sequence[15]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14};
//全排列
sum=99999999;
operate(sequence,1);
printf("%d\n",sum);
}
return 0;
}
④方法二:TSP之动态规划
可以用搜索,搜索必然超时,网上查一查可以用——记忆化搜索,记忆化搜索能做的动规就能做,写递归太麻烦了于是动规!题目中起点终点确定,我们可以考虑用一个二维dp数组来保存一个状态——dp[i]{V}表示从结点0到结点 i 途经V中所有节点的最短路径长(这里的V是一个集合),若V是最大值,这表示从0开始,访问完所有节点,最后以i节点结束。
于是状态转移方程可以为:dp[i]{V}=min( dp[i]{V} , dist[i][j]+dp[j]{V-{j}} ) (j 属于 V)
大思路定好了,我们来考虑细节部分,主要有以下部分:
1)建图等等:构建Map;
2)集合V的表示:二进制数,即010表示三个数的集合第二个有,其余无;
3)这里在dp时,起始节点和终止节点都单独处理;想一想如何修改成经典的tsp问题。只需要n+1,最后遍历时
ans=min(ans,dp[i][cnt-1]+Map[i][0])
#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int T,n;
int a[15][2];
int Map[15][15];
int dp[15][1<<10];
/**
1<<0 1 1
1<<1 2 10
1<<2 4 100
1<<3 8 1000
*/
int main(){
freopen("1.txt","r",stdin);
freopen("2.txt","w",stdout);
scanf("%d",&T);
while(T--){
scanf("%d",&n);
scanf("%d%d",&a[0][0],&a[0][1]);
scanf("%d%d",&a[n+1][0],&a[n+1][1]);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i][0],&a[i][1]);
}
for(int i=0;i<n+2;i++){
for(int j=0;j<n+2;j++){
//两点之间:曼哈顿距离
int dx=(a[j][0]-a[i][0])>0?(a[j][0]-a[i][0]):(a[i][0]-a[j][0]);
int dy=(a[j][1]-a[i][1])>0?(a[j][1]-a[i][1]):(a[i][1]-a[j][1]);
Map[i][j]=Map[j][i]=dx+dy;
}
}
n=n+2;
int cnt=1;
//若n+2是7,这里去掉起始节点
//2 4 8 16 32 32~100000
for(int i=2;i<n;i++) cnt<<=1; //组合数(除起点终点外)
printf("%d\n",cnt);
for(int i=0;i<n;i++) //初始化
for(int j=0;j<cnt;j++)
dp[i][j]=99999999;
for(int i=0;i<n;i++) //起点确定,定下初始条件
dp[i][0]=Map[i][0];
for(int i=1;i<cnt;i++) //从所有元素考虑起
for(int j=1;j<n-1;j++){
for(int k=1;k<n-1;k++){
if((1<<k-1)&i) //k is in the set
dp[j][i]=min(dp[j][i],Map[j][k]+dp[k][i-(1<<k-1)]); //状态转移方程
}
}
int ans=99999999;
for(int i=1;i<n;i++)
ans=min(ans,dp[i][cnt-1]+Map[i][n-1]);
printf("%d\n",ans);
}
return 0;
}