此文出处: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;
}