先来了解一个经典问题:TSP
一个n个点的带权的有向图,求一条路径,使得这条路经过每个点恰好一次,并且路径上边的权值和最小(或者最大)。或者求一条具有这样性质的回路,这是经典的TSP问题。
n <= 16 (重要条件,状态压缩的标志)
如何表示一个点集:
由于只有16个点,所以我们用一个整数表示一个点集:
例如:
5 = 0000000000000101;(2进制表示)
它的第0位和第2位是1,就表示这个点集里有2个点,分别是点0和点2。
31 = 0000000000011111; (2进制表示)
表示这个点集里有5个点,分别是0,1,2,4,5;
所以一个整数i就表示了一个点集;整数i可以表示一个点集,也可以表示是第i个点。
状态表示:
dp[i][j]表示经过点集i中的点恰好一次,不经过其它的点,并且以j点为终点的路径,权值和的最小值,如果这个状态不存在,就是无穷大。
状态转移:
单点集:状态存在dp[i][j] = 0;否则无穷大。非单点集:
1:状态存在 dp[i][j] = min(dp[k][s] + w[s][j])
k表示i集合中去掉了j点的集合,s遍历集合k中的点并且dp[k][s]状态存在,点s到点j有边存在,w[s][j]表示边的权值。
2.:状态不存在 dp[i][j]为无穷大。
最后的结果是: min( dp[( 1<<n ) – 1][j] ) ( 0 <= j < n );
技巧:利用2进制,使得一个整数表示一个点集,这样集合的操作可以用位运算来实现。例如从集合i中去掉点j:
一个n个点的带权的有向图,求一条路径,使得这条路经过每个点恰好一次,并且路径上边的权值和最小(或者最大)。或者求一条具有这样性质的回路,这是经典的TSP问题。
n <= 16 (重要条件,状态压缩的标志)
如何表示一个点集:
由于只有16个点,所以我们用一个整数表示一个点集:
例如:
5 = 0000000000000101;(2进制表示)
它的第0位和第2位是1,就表示这个点集里有2个点,分别是点0和点2。
31 = 0000000000011111; (2进制表示)
表示这个点集里有5个点,分别是0,1,2,4,5;
所以一个整数i就表示了一个点集;整数i可以表示一个点集,也可以表示是第i个点。
状态表示:
dp[i][j]表示经过点集i中的点恰好一次,不经过其它的点,并且以j点为终点的路径,权值和的最小值,如果这个状态不存在,就是无穷大。
状态转移:
单点集:状态存在dp[i][j] = 0;否则无穷大。非单点集:
1:状态存在 dp[i][j] = min(dp[k][s] + w[s][j])
k表示i集合中去掉了j点的集合,s遍历集合k中的点并且dp[k][s]状态存在,点s到点j有边存在,w[s][j]表示边的权值。
2.:状态不存在 dp[i][j]为无穷大。
最后的结果是: min( dp[( 1<<n ) – 1][j] ) ( 0 <= j < n );
技巧:利用2进制,使得一个整数表示一个点集,这样集合的操作可以用位运算来实现。例如从集合i中去掉点j:
k = i & (~( 1<<j)) 或者 k = i - (1<<j)
hdu-5067
题意:给一个n*m方阵,有的点不为0代表有石头,为0代表没石头,求从(0,0)点经过所有有石头的点再回到(0,0)点的最小步数。
比tsp问题多了限制条件即起始点必须为(0,0),终点也为(0,0)。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<string.h>
using namespace std;
#define N 2048
#define inf 3000
int n,m;
int tot;
int x[15],y[15];
int dp[N][12];
int main()
{
while(~scanf("%d%d",&n,&m))
{
tot=0;
int k;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
scanf("%d",&k);
if(k||i==0&&j==0){
x[tot]=i;
y[tot++]=j;//tot个必须走的点,有石头的点和起点
}
}
dp[1][1]=0;
for(int i=3;i<(1<<tot);i+=2)
if(i&1){
for(int j=1;j<tot;j++)
{
int mi=inf;
if(i&(1<<j)){
int s=i-(1<<j);
for(int k=0;k<tot;k++)
{
if(k==0&&s!=1) continue;//保证起点为第一个点即(0,0)
if(s&(1<<k)) mi=min(mi,dp[s][k+1]+abs(y[k]-y[j])+abs(x[k]-x[j]));
}
}
dp[i][j+1]=mi;
}
}
//printf("%d\n",dp[(1<<tot)-1][tot]);
int mi=inf;
int s=(1<<tot)-1;
if(tot==1) mi=0;
for(int i=1;i<tot;i++)
mi=min(dp[s][i+1]+x[i]+y[i],mi);//返回终点(0,0)的最小步数
printf("%d\n",mi);
}
return 0;
}