nyoj-1182旅游【三进制状态压缩dp】

这个是经典状态压缩dp tsp问题的变形

首先来看看《挑战程序设计竞赛》讲解tsp问题:


给定一个n个定顶点组成的带权有向图的距离矩阵d(i,j)(INF表示没有变)。要求从顶点0出发,经过每个顶点恰好一次后再回到顶点0。问所经过得边的总权重的最小值是多少?

限制条件

1<n<16

0<d(i,j)<1000

tsp问题是np困难的。没有已知的的多项式时间的高效算法可以解决这一问题。

所有可能的路线共有(n-1)!种。这是个非常大的值。即使在本题中n已经很小了。仍然无法试遍每一种情况。对于这个问题我们可以使用dp来解决。首先让我们试着写出它的递推式。

假设现在已经访问过得顶点的集合(起点0当做还未访问过的顶点)为S.当前所在顶点为v,用dp[S][v]表示从v出发访问剩余的所有顶点。最终回到顶点0的路径的权重总和的最小值。由于从v出发可以移动到任意一个节点u不属于S,因此有如下递推式:

dp[V][0]=0;

dp[S][v]=min{dp[S∪{u}][u]+d(v,u)|u不属于S};

我只要按照这个递推式进行计算就可以了。这样就可以在O(2^n*n^2)的时间里完成计算。


旅游

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述

有n个城市,一个人去旅游,他想游遍n个城市,且每个城市最多去两次,问最短的路程是多少。

起点任意,每两个城市之间可能会有多条路,也可能会没有路。如果能游遍所有的城市

输出最少的,不能的话输出-1
输入
有多组测试数据,每组都有一个正整数n和m(n>1&&n<=10,m>0)
n表示由n个城市,接下来有m行,每行三个正整数,u,v,w表示
u和v之间有一条长为w的路(w>=0&&w<300)。
输出
每组数据输出一行,如果能游遍所有的城市,输出最短的距离,如果不能,输出-1
样例输入
样例输入

这道题与经典tsp问题用的是同一种dp递推式。区别是:(1)不需要回路(2)顶点限制的次数+1

经典tsp问题它要求顶点只能遍历一次,所以对于dp的状态,记录的是该顶点的入度。而在这道题里入度的要求则变成了indegree小于等于2.所以问题就从经典tsp的二进制的状态压缩变成了三进制的状态压缩.

对于三进制的状态转移因为不能用位运算了,所以就要对三进制的操作进行预处理并用数组保存。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define Max_V 12
#define INF 0x3f3f3f3f
int n,m;
int g[Max_V][Max_V];
int ter[60000][Max_V];//ter[S][v]表示在S状态中v顶点的入度
int bit[]={1,3,9,27,81,243,729,2187,6561,19683,59049};
int dp[60000][Max_V];
int findMin(int x,int y)
{
	return x>y?y:x;
}
void ternary_init()
{//对三进制的操作做预处理
<span style="white-space:pre">	</span>int i, j, b; 
    for(i=0;i<bit[10];i++) 
    { 
        b=i; 
        for(j=0;j<10;j++) 
        { 
            ter[i][j] =b % 3; 
            b/=3; 
        } 
    } 
}
void init()
{//初始化
	memset(g,-1,sizeof(g));
	memset(dp,INF,sizeof(dp));
	for(int i=0;i<n;i++)
		dp[bit[i]][i]=0;
}
int main()
{
	int i;
	int u,v,w;
	ternary_init();
	while(~scanf("%d%d",&n,&m))
	{
		init();
		for(i=0;i<m;i++)
		{
			scanf("%d%d%d",&u,&v,&w);
			if(g[u-1][v-1]>w||g[u-1][v-1]==-1)
				g[u-1][v-1]=g[v-1][u-1]=w;
		}
		int ans=INF;
		for(int S=0;S<bit[n];S++)
		{//遍历所有状态
			int flag=true;
			for(u=0;u<n;u++)
			{
				if(ter[S][u]==0)
					flag=false;
				if(dp[S][u]==INF)
					continue;
				for(v=0;v<n;v++)
				{
					if(u==v||g[u][v]==-1||ter[S][v]>=2)
						continue;
					dp[S+bit[v]][v]=findMin(dp[S+bit[v]][v],dp[S][u]+g[u][v]);//状态转移
				}
			}
			if(flag)
			{//所有顶点的入度都大于0
				for(u=0;u<n;u++)
					ans=findMin(ans,dp[S][u]);
			}
		}
		if(ans==INF)
			printf("-1\n");
		else printf("%d\n",ans);
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值