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