[ L i n k \frak{Link} Link] | 从 0 \frak{0} 0点出发,经过每个点至少一次,回到 0 \frak{0} 0点。求经过的最小距离。点数 1 ≤ n ≤ 10 \frak{1\le n\le 10} 1≤n≤10。
当然不可能考虑整个回路怎么搞。
回路经常有一种方法,就是把
0
\frak{0}
0点拆出来,拆出一个起点和一个终点
最短回路就是从起点经过所有点到达终点。
“怎么做?”
先不要什么都没有就去考虑问题。记录一个状态,
f
(
s
t
a
t
e
)
\frak{f(state)}
f(state)表示从起点到达
s
t
a
t
e
\frak{state}
state经过的最短距离。
不过只有一维是不够的,还要记录下当前所在位置,所以应该是
f
(
p
o
s
i
t
i
o
n
,
s
t
a
t
e
)
\frak{f(position,state)}
f(position,state)。
转移到下一个
p
o
s
i
t
i
o
n
′
\frak{position'}
position′的时候就合并上
p
o
s
i
t
i
o
n
→
p
o
s
i
t
i
o
n
′
\frak{position\to position'}
position→position′的最短路……?
这么讲意味着要合并最短路的距离和经过的点。可是真的要这样吗?
最短路经过的点不好合并进去。——如果不去考虑最短路经过的所有点呢?
那就要考虑最短路径经过的点是个什么情况了。
记为x。假如说在之前的状态里面,x点已经被访问过了,那当然不用管它;
如果x点没有被访问过?——那也不用去管。
既然x点没有被访问过,那么访问过x点的状态就在未来的状态里面。
所以说根本不用管,这就是重复状态而已,如果复杂度已经够了也不用想怎么优化掉它
如果非要去合并上最短路经过的所有点,可能还会更不好搞。
最终时间复杂度
Θ
(
n
2
2
n
)
\frak{\Theta(n^22^n)}
Θ(n22n)。
不过其实还有另外一种做法,注意到范围是10,我们又知道11以内都可以阶乘复杂度过1s,那可能可以全排列水过。
#Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<ctime>
using namespace std;
int F[262150][20]={};
int A[20][20]={};
int n;
void out(int x)
{
while(x)
{
cout<<(x&1);x>>=1;
}
cout<<" ";
}
int main()
{
while(~scanf("%d",&n))
{
if(!n)return 0;
n+=2;
for(int i=1;i<n;++i)
{
for(int j=1;j<n;++j)
{
scanf("%d",&A[i][j]);
if(A[i][j]<0)A[i][j]=0;
}
}
for(int i=1;i<=n;++i)A[i][n]=A[i][1],A[n][i]=A[1][i];
A[n][n]=0;
for(int k=1;k<=n;++k)
{
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
{
if(A[i][j]<=A[i][k]+A[k][j])continue;
A[i][j]=A[i][k]+A[k][j];
}
}
}
memset(F,0x3f,sizeof(F));
F[1][1]=0;
for(int i=1;i<=(1<<n)-1;++i)
{
for(int j=1;j<=n;++j)
{
if(F[i][j]==0x3f3f3f3f)continue;
if(!(i&(1<<j-1)))continue;
for(int v,k=1;k<=n;++k)
{
if(i&(1<<k-1))continue;
v=i|(1<<k-1);
F[v][k]=min(F[v][k],F[i][j]+A[j][k]);
}
}
}
printf("%d\n",F[(1<<n)-1][n]);
}
return 0;
}