入门OJ:售货员的难题

题目描述

某乡有n个村庄(1<n<15),有一个售货员,他要到各个村庄去售货,各村庄之间的路程s(0<s<1000)是已知的,且A村到B村与B村到A村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为1,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。

输入格式

村庄数n和各村之间的路程(均是整数)。

输出格式

最短的路程


走完n个点的最短路可以由走完n-1个点的最短路加上最后走的边的边权更新而来,所以不难想到我们可以用动规来做这题。而一个点只能被经过一次,并且n<15,我们可以用状压。

设dp(i,j)表示对图的遍历情况为i的二进制数时走到点j的最短路。考虑什么状态可以更新dp(i,j)。

由于每个点只能被经过一次,设更新i的状态为k,那么显然i的第j位为1,k的第j位为0,并且除此之外k和i没有别的不同。所以我们不需要枚举k,只需i^(1<<j)即可。如果k能够更新i,那么肯定是从k的情况时经过的点走过来,也就是k中有1的位置。那么:

\[dp(i,j)=Min{\{}dp(iXOR(1<<j),k){\}} \]

其中k和i满足上述的所有条件。

#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 16
using namespace std;
 
int dis[maxn][maxn],dp[1<<maxn][maxn];
int n;
 
int main(){
    scanf("%d",&n);
    for(register int i=0;i<n;i++){
        for(register int j=0;j<n;j++) scanf("%d",&dis[i][j]);
    }
    memset(dp,0x3f,sizeof dp),dp[1][0]=0;
    for(register int i=1;i<1<<n;i++){
        for(register int j=0;j<n;j++) if((i>>j)&1){
            for(register int k=0;k<n;k++) if(j!=k&&((i^(1<<j))>>k)&1){
                dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+dis[k][j]);
            }
        }
    }
 
    int mini=0x3f3f3f3f;
    for(register int i=1;i<n;i++) mini=min(mini,dp[(1<<n)-1][i]+dis[i][0]);
    printf("%d\n",mini);
    return 0;
}

时间复杂度为O(N^2 * 2^N)

posted @ 2019-05-20 21:27  修电缆的建筑工  阅读(160)  评论(0编辑  收藏  举报