POJ3311

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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值