3.4熟练掌握动态规划

状态压缩DP

旅行商问题 TSP

TSP问题是NP困难的,没有已知的多项式时间得高效算法可以解决这一问题,不过在程序设计竞赛中还是有可能出现这种范围比较小的题目的。
所有可能的路线共有(n-1)~.对于这个问题,我们可以用DP来解决。
首先我们先试着写出它的递推式。
假设现在已经访问过的顶点的集合(起点0当作还问访问过的顶点)为S,当前所在的顶点为v,用dp[s][v]表示从v出发访问剩余的所有顶点,最终回到顶点0的路径的权重总和的最小值,由于从v出发可以移动到任意的一个结点u不属于S,因此有如下递推式
dp[V][0]=0
dp[S][v]=min(dp[S∪{u}]+d(u,v)|u不属于S)
我们只要按照这个递推式进行计算就可以了,由于在这个递推式中,有一个下表是集合而不是普通的整数,因此需要稍加处理。首先我们试着使用记忆化搜索求解。虽然有一个下标不是整数(集合S),但是我们可以把它编码为整数。特别的,对于集合我们可以把每一个元素的选取与否对应到一个二进制位里面,从而把状态压缩成一个整数,大大方便了计算和维护。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=16;
int d[maxn][maxn];
int dp[1<<maxn][maxn];//记忆化搜索使用的数组
int n;
int rec(int S,int v){
	if(dp[S][v]>=0)return dp[S][v];
	if(S==(1<<n)-1&&v==0){//访问了所有节点并且已经返回0号点 
		return dp[S][v]=0;
	}
	int res=INF;
	for(int u=0;u<n;u++){
		if(!((S>>u)&1)){//未访问过 
			res=min(res,rec(S|1<<u,u)+d[v][u]);//将u加入到集合S中 
		}
	}
	return dp[S][v]=res;
}
void solve(){
	memset(dp,-1,sizeof(dp));
	cout << rec(0,0) << endl;
}
int main(){
	cin >> n;
	memset(d,0x3f,sizeof(d));
	int m;
	cin >> m;
	for(int i=0;i<m;i++){
		int x,y,c;
		cin >> x >> y >> c;
		d[x][y]=c;
	}
	solve();
	return 0;
}
/*
5
8
0 1 3
0 3 4
1 2 5
2 0 4
2 3 5
3 4 3
4 1 6
4 0 7


22
*/

这样就可以在O(2nn2)的时间内完成计算。对于不是整数的情况,很多时候很难确定一个合适的递推顺序,因此使用记忆化搜索可以避免这个问题。不过在这个问题种,对于任意两个整数i和j,如果它们对应的集合满足S(i)包含于S(j),就有i<=j,因此还可以像下面的情况写法一样,通过循环求出答案。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=16;
int d[maxn][maxn];
int n;
int dp2[1<<maxn][maxn];
void solve2(){
	memset(dp2,0x3f,sizeof(dp2));
	dp2[(1<<n)-1][0]=0;
	for(int S=(1<<n)-2;S>=0;S--){
		for(int v=0;v<n;v++){
			for(int u=0;u<n;u++){
				if(!(S>>u&1)){
					dp2[S][v]=min(dp2[S][v],dp2[S|1<<u][u]+d[v][u]);
				}
			}
		}
	} 
	cout << dp2[0][0] << endl;
}
int main(){
	cin >> n;
	memset(d,0x3f,sizeof(d));
	int m;
	cin >> m;
	for(int i=0;i<m;i++){
		int x,y,c;
		cin >> x >> y >> c;
		d[x][y]=c;
	}
	solve2();
	return 0;
}

类似于这种针对集合的DP我们叫做状态压缩DP。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值