环游世界

环游世界

有n个地方(标号1到n)要从标号为0的地方出去,经过所有的地方之后回来,求最短的时间,输入(n+1)*(n+1)的矩阵a,a[i][j]a[i][j]表示顶点ii到顶点jj所需要的时间。

输入

第一行输入一个整数n (1 le n le 10)n(1n10)。

接下来n + 1n+1行,每行n + 1n+1个整数,表示矩阵中的元素,矩阵中的元素在区间[1, 1000][1,1000]内。

输出

输出最短时间。

样例

输入

复制
3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0

输出

复制
8

提示

子任务1,30分,1 <= n <=9

子任务2,70分,全范围。

分析

状态压缩DP模板。

那么,什么是状压dp呢?

简单来说,就是基于集合的dp。本题中状态转移取决于走过的点集和当前位置(最终答案由走完了所有点的状态再回到起点)。

状压就在于记录走过的点集的状态,相当于用二进制表示。

假设0表示该点没走过,1表示走过,对于一个四个点的情况,共有16种状态

0000

0001

0010

0011

.......

1111

那么,对于n个点的情况,每个点都有选和不选两种情况,共有2^n种情况

所以dp中记录点集状态的那一维开成1<<n

代码

#include<bits/stdc++.h>
using namespace std;
int n;
int dp[1<<14][14];
int dis[14][14];
void checkmin(int &x,int y)
{
    if(x==-1||x>y)
        x=y;
}
int main()
{
    while(scanf("%d",&n)!=EOF){
    
        if(!n) break;
            n++;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            scanf("%d",&dis[i][j]);
    for(int k=0;k<n;k++)
         for(int i=0;i<n;i++)
             for(int j=0;j<n;j++)
                 if(dis[i][j]>dis[i][k]+dis[k][j])
                     dis[i][j]=dis[i][k]+dis[k][j];
    memset(dp,-1,sizeof(dp));
    dp[1][0]=0; 
    for(int mask=1;mask<(1<<n) ;mask++)
        for(int end=0;end<n;end++)
            if(dp[mask][end]!=-1)
            {
                for(int nxt=0;nxt<n;nxt++)
                {
                    if(! (mask&(1<<nxt)) )
                    {
                        checkmin(dp[mask|(1<<nxt)][nxt],dp[mask][end]+dis[end][nxt]);
                        
                    }
                }
            }
    int ans=-1;
    for(int end=0;end<n;end++)
    {
        if(dp[(1<<n)-1][end]!=-1)//此处的特殊判断要注意!
        checkmin(ans,dp[(1<<n)-1][end]+dis[end][0]);
    }
    printf("%d",ans);
}    
    return 0;
}
原文地址:https://www.cnblogs.com/conprour/p/14728922.html