售货员的难题
题目描述
某乡有n个村庄(1<n<15),有一个售货员,他要到各个村庄去售货,各村庄之间的路程s(0<s<1000)是已知的,且A村到B村与B村到A村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为1,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。
【输入】
村庄数n和各村之间的路程(均是整数)。
【输出】
最短的路程。
【样例】
Input
3 {村庄数}
0 2 1 {村庄1到各村的路程}
1 0 2 {村庄2到各村的路程}
2 1 0 {村庄3到各村的路程}
Output
3
讲解
首先,此题和最短Hamilton路径类似,状态设计几乎一样。
状态转移方程
F [ j , ( ( 1 < < ( j − 1 ) ) ∣ i ) ] = m i n ( F [ j , ( ( 1 < < ( j − 1 ) ) ∣ i ) ] , F [ k , i ] + w [ k , j ] ) F[j,~(~\color{green}{(1 << (j-1)~)}\color{black} | i)] = min(F[j,~(\color{green}{(1 << (j-1)) }\color{black}| i)],F[k,i] + w[k,j]) F[j, ( (1<<(j−1) )∣i)]=min(F[j, ((1<<(j−1))∣i)],F[k,i]+w[k,j])
解释:
i < < ( j − 1 ) i<<(j-1) i<<(j−1)即作第j个村庄的状态形式, ∣ i |i ∣i(即 o r i or~i or i)意思是,如果当前状态没走过,则走;走过了,则不重复走,防止进位改变本意。
注意
因为1节点一定会走,所以我们将它当做0号节点,这样可以节省一半的时间和空间。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, w[20][20], ans = 0x3f3f3f3f;
int dp[1 << 19][20];
int main()
{
scanf("%d", &n);
memset(dp, 0x3f, sizeof(dp));
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
scanf("%d", &w[i][j]);
for(int i = 2; i <= n; ++i)
dp[1 << (i - 2)][i - 1] = w[0][i - 1];
int len = 1 << (n - 1);
for(int i = 1; i < len; ++i)
for(int j = 1; j < n; ++j)
if(!(1 << (j - 1) & i))
for(int k = 1; k < n; ++k)
if(1 << (k - 1) & i)
dp[1 << (j - 1) | i][j] = min(dp[1 << (j - 1) | i][j], dp[i][k] + w[k][j]);
for (int i = 1; i < n; ++i)
ans = min(ans, dp[len - 1][i] + w[i][0]);
printf("%d", ans);
return 0;
}
最后考大家一道“编程”题:
我们能且只能将一个变量名修改为另一个变量名,在无法打字、选中的情况下,至少需要修改几次才能将下列代码的循环变量顺序修改为i, j, k?
欢迎评论、留言!
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
cin >> num[k][i][j];
完结,撒花