Luogu 1171 售货员的难题(状态压缩动态规划)

此文出处:http://www.cnblogs.com/wdvxdr/p/7253593.html,此乃左神所写博客文章,特此转载学习,感谢!

题目描述

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

写在题解前的话

对于这道题来说,最好的解法应该是状态压缩动态规划,但实际上大部分人写都TLE了两个点,AC的都是DFS+剪枝做的,速度仍比优化过的动态规划快不少,但为什么还要用动态规划做呢,因为做题不是为了AC,而是为了掌握和巩固算法知识。。。

80分题解

用一个二进制数表示当前状态,例如二进制数1011表示第1个,第2个,第4个村庄已经到达过了,然后运用位运算的思想写状态转移方程,这题的状态是f[s][i]表示走过用s表示的村庄后最终到达i点的最优解,那么状态转移方程就是:f[s][i] = min(f[s^(1<<(i-1))][j]+a[j][i],f[s][i]);其中(1<<(i-1)用于表示每一座村庄状压后是哪一个二进制数(如村庄1是1,村庄2是10,村庄3是100。。。)。

对于f[1<<(i-1)|1][i]只经过了第1个,和第i个村庄,自然是等于第1个和第i个村庄的距离了。

复制代码
#include<cstdio>
#include<iostream>
#include<cstring>
#define INF 10000000
using namespace std;
int n,a[22][22],f[1048580][22];

int read()//读入优化
{
    int ans=0;char ch = getchar();
    while(ch<'0'||ch>'9')    ch = getchar();
    while(ch>='0'&&ch<='9') ans= ans*10+ch -'0',ch = getchar();
    return ans; 
}

int main()
{
    n = read();
    int all = (1<<n)-1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            a[i][j] = read();
    memset(f,127/3,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;++i)
        f[1<<(i-1)|1][i] = a[1][i];//只经过了两个村庄
    for(int s=0;s<=all;++s)//枚举每一种状态
        for(int i=1;i<=n;++i)//枚举最终到达的每一个城市
            if(s&(1<<(i-1)))
                for(int j=1;j<=n;++j)//枚举上一个到达的城市
                    f[s][i] = min(f[s^(1<<(i-1))][j]+a[j][i],f[s][i]);
    int ans = f[(1<<n)-1][2]+a[2][1];
    for(int i=2;i<=n;i++)
        ans = min(ans,f[all][i]+a[i][1]);//寻找最优解
    printf("%d",ans);
    return 0;
}
复制代码

90分题解

80分到90分也没什么优化的,只是不用STL库的min函数,而是用#define(用内联函数优化不了多少)。

100分题解

如果我们注意到的话,不管对于什么状态,第1个村庄一定是经过的,所以我们枚举状态时可以直接用3开始枚举,每次状态+2,这样就可以保证每次状态都一定经过第1个村庄。

当我们枚举枚举每个状态最终到达的城市时,不需要枚举到n,只需枚举到当前状态最高位就可以了。我们可以定义一个k,代表需要枚举城市的个数,p用来代表2的k次方,只用当当前状态大于p时,我们才让k++,并让p乘2.

复制代码
#include<cstdio>
#include<iostream>
#include<cstring>
#define INF 10000000
#define min(a , b) ((a) < (b) ? (a) : (b))
#define lowbit(x) x&-x
using namespace std;
int n,a[22][22],f[1048580][22];

int read()
{
    int ans=0;char ch = getchar();
    while(ch<'0'||ch>'9')    ch = getchar();
    while(ch>='0'&&ch<='9') ans= ans*10+ch -'0',ch = getchar();
    return ans; 
}

int main()
{
    n = read();
    int all = (1<<n)-1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            a[i][j] = read();
    memset(f,127/3,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;++i)
        f[1<<(i-1)|1][i] = a[1][i];
    for(int s=3,p=4,k=2;s<=all;s+=2)
    {
        if(s > p)
            p = p << 1 , k++;//记录需要枚举城市的个数
        for(int i=1;i<=k;i++)//只需枚举到k,无需枚举到n
            if(s&(1<<(i-1)))
            { 
                int r = s^(1<<(i-1)); 
                for(int j=1;j<=k;j++)//只需枚举到k,无需枚举到n
                    f[s][i] = min(f[r][j]+a[j][i],f[s][i]);
            } 
    }
    int ans = f[(1<<n)-1][2]+a[2][1];
    for(int i=2;i<=n;i++)
        ans = min(ans,f[all][i]+a[i][1]);
    printf("%d",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xuechen_gemgirl/article/details/79445153