POJ3311 Hie with the Pie
给你一个有n+1(1<=n<=10)个点的有向完全图,用矩阵的形式给出任意两个不同点之间的距离。(其中从i到j的距离不一定等于从j到i的距离)现在要你求出从0号点出发,走过1到n号点至少一次,然后再回到0号点所花的最小时间。
输入:包含多组实例。每个实例第一个为n,然后是n+1行矩阵,每行矩阵有n+1个数字,第i行第j个数字表示从i-1到j-1号点的距离。当输入n=0时表示输入结束。
输出:最小距离。
分析;本题和经典TSP旅行商问题类似,但是TSP要求每个节点仅走过1次,本题要求每个节点至少走过一次。
同样的是本题也用状态压缩DP来解。令d[i][s]表示从0号点出发,走过集合S中的所有点(不包含起始0号点,但可以包含终止点0号点),且当前在i号点时所需的最小距离。
则:d[i][s]= min{ d[ j ][ s –{i}]+min_dist[j][i] } 其中min_dist[j][i]是指从j节点到i节点的最小距离, s表示节点集合,且包含i和j。
初值:d[ 0 ][ {} ] = 0,我们所求最小距离为:d[0][{0,1,2,3,…n}]
接下来我们来论证一下该题的正确性:首先d[0][{0,1,2,3,…n}]表示最终走过所有节点至少1次回到0号点的最小距离。则当走最后一步从i到0时,则必然d[0][{0,1,2,3,…n}] 是从所有的 d[i][{1,2,3,…n}] + min_dist[i][0]中选一个最小的值,1<=i<=n。
d[i][{1,2,3,…n}]也必然是从所有的d[j][{1,2,3…n}-{i}] + min_dist[j][i]中选一个最小的值,以此类推。所以行走最短路线的过程只能是我们上面的分析过程,不可能有其他方式,如果存在最短路径我们必然可以通过以上解法求出来。
由于本题要求的是min_dist[i][j]即两点间的最短距离,且不在乎重复走过一些点,所以需要先用floyd算法先求出任意两点间的最小距离。
有个疑问,假设d[2][{1,2}]= 10,且dist[2][3]=10(2到3的真实距离为10)假设min_dist[2][3]=5,因为2到1再到3的距离为5,所以我们会求得d[3][{1,2,3}]=15吗?此时我们走了两次1号节点,有没有可能d[3][{1,2,3}]的值更小一点,而我们却漏了这个解?不可能的,我们当前走的路线是0->1->2->1->3得到的15,如果0->2->1->3得到的值小于15那么d[3][{1,2,3}]的更小值肯定能从d[1][{1,2}]得到更新。所以这个递推方程是不会丢失最优解的。
本题根据状态转移方程可知用记忆搜索比较方便。
AC代码:0MS
#include<cstdio>
#include<cstring>
using namespace std;
int n;
int dist[15][15];//输入的距离矩阵
int min_dist[15][15];//求出的最短路径距离矩阵
int d[15][1<<15];//用于表示DP状态
bool vis[15][1<<15];
int dp(int i,int s)//记忆话搜索,这个dp函数不会计算dp(0,0)的切记
{
if(vis[i][s])return d[i][s];
vis[i][s]=true;
int &ans = d[i][s];
ans = 1e9;
for(int j=0;j<=n;j++)if(s&(1<<j)&&j!=i)//集合s中的一个节点号j,且只要S不是全1,j就娶不到0
{ //需要单独初始化d[i][1<<i]的值
if(ans>dp(j,s^(1<<i))+min_dist[j][i])//当前距离小于子集生成的距离
{
ans = dp(j,s^(1<<i))+min_dist[j][i];
}
}
return ans;
}
void floyd()//计算最短路径距离
{
for(int k=0;k<=n;k++)
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
if(min_dist[i][j]>min_dist[i][k]+min_dist[k][j])
min_dist[i][j] = min_dist[i][k]+min_dist[k][j];
}
int main()
{
while( scanf("%d",&n)==1&&n )
{
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
{
scanf("%d",&dist[i][j]);
min_dist[i][j]= dist[i][j];
}
floyd();//计算最短路径距离
memset(vis,0,sizeof(vis));
d[0][0]=0;
vis[0][0]=true;
for(int i=1;i<=n;i++)//注意这里一定要单独初始化,否则dp函数计算出来的结果错误
{
vis[i][1<<i]=true;
d[i][1<<i]=min_dist[0][i];
}
printf("%d\n",dp(0,(1<<(n+1))-1 ));
}
return 0;
}