Travelling HDU - 3001(状压DP,三进制状态压缩)

Travelling

 题目链接:HDU - 3001 

题意:N座城市,M条边,可以选任意城市作为起点,每座城市最多走两次,问走完所有城市的最小花费,花费即边的权值;

思路:一开始没认真读题,傻不拉唧的看成每个城市只能走一次,果断WA了~~~是两次!!!两次!!!!,所以每座城市就有三种状态:0,1,2,分别表示未走过,走过一次,做过两次,所以是三进制存状态;与二进制的不同点在于不能直接位运算,所以要自己把位运算的过程给写出来,这是此题唯一难点,除此之外就和二进制的一样了;

还是用dp[i][j]表示i状态下最终到达j城市的最小消耗;初始化为:i表示只有第j位为1的状态,dp[i][j]=0;因为可以以任意城市作为起点;当每个位都不是0的时候表示所有城市都经过了;

说一下三进制的位运算:

1:若state(i)表示只有第i位为1,这个数怎么表示呢?就是3^(i-1);

2:i状态是否在j位不为0;i一直除3,j-1次后取模3,即可判断j位是否为0;

3:i-state(j)表示第j位减1;

4:判断是否经过了所有城市:i一直对3取模,再除以3,直到i=0;每次取模,若余数大于0,cnt++,最终的cnt值就是已经经过的城市的个数;

一点注意:存边的时候,要一只更新,因为会出现重边,我们只取最小边;

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
long long dis[15][15], dp[60000][15];
//返回只有第i位为1的数;
int state(int i){
	int p=1, cnt=1;
	while(cnt<i){
		p*=3;
		cnt++;
	}
	return p;
}
//x状态下是否存在y状态;
bool check(int x, int y){
	while(x&&y){
		if(x%3&&y%3) return true;
		x/=3, y/=3;
	}
	return false;
}
//判断是否遍历了所有城市;
bool ok(int k, int n){
	int cnt=0;
	while(k){
		if(k%3)
			cnt++;
		k/=3;
	}
	if(cnt<n) return false;
	return true;
}
int main(){
	int n, m;
	while(~scanf("%d%d", &n, &m)){
		memset(dp, INF, sizeof(dp));
		memset(dis, INF, sizeof(dis));
		for(int i=0; i<m; i++){
			int a, b;
			long long c;
			scanf("%d%d%lld", &a, &b, &c);
			dis[a][b]=dis[b][a]=min(c, dis[a][b]);
		}
		for(int i=1; i<=n; i++){
			dp[state(i)][i]=0;
		}
		long long ans=INF;
		int p=state(n+1);
		for(int i=1; i<p; i++){
			for(int j=1; j<=n; j++){
				if(check(i, state(j))){
					for(int k=1; k<=n; k++){
						if(j==k) continue;
						if(check(i, state(k))){
							dp[i][j]=min(dp[i][j], dp[i-state(j)][k]+dis[k][j]);
							if(ok(i, n)) ans=min(dp[i][j], ans);//如果已经走遍了n个城市,更新答案;
						}
					}
				}
			}
		}
		if(ans>=INF) printf("-1\n");
		else printf("%lld\n", ans);
	}
	return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值