HDU 3001 Travelling 状态压缩+暴力

3 篇文章 0 订阅

题目链接

题目描述 Description
给你一个N个点M条边的无向带权图。起点是任意的,要求每个点最多走两次,把所有的点遍历完一遍,费用最小是多少,注意有重边(被坑到)。
输入描述 Input Description
有多组输入数据,第一行给出一个N(1<=N<=10)和一个M分别表示需要拜访的城市数和边数。接下来M行,每一行给出a,b,c三个数表示a与b之间有一条花费c的路。输入一直读到EOF为止
输出描述 Output Description
对于每个样例输出一个整数表示最小花费,如果没有办法遍历所有城市则输出-1.
水题分析 Waterproblem Analysis
首先观察题目数据范围,N只有10个,要求每个城市至少拜访一遍,至多去两次,那么就可以用一个N位的3进制数代表当前的情况,每一位0,1,2三个数来表示该城市拜访的次数,这种全部拜访一遍的题经常采用这种方法储存状态要归纳总结。节省空间,用一个int就可以。同时我们可以预处理出每个数字代表的状态来节省时间。当然这个题还要记录到达这个这个状态时在哪一个城市,否则会过度减枝,举个例子。很明显按1->2->3的顺序遍历和2->1->3的顺序来遍历得到的答案是不同的所以要再记录当前遍历到的点是哪个。题目中未说明起点是哪个点所以要分别以每一个点为起点进行搜索。如果当前的答案已经超过算出的答案可以进行剪枝,同时要用邻接矩阵保存图只保留两点之间的最短边,否则会因为入队点数过多爆内存。在这里插入图片描述

附上代码:

#include<iostream>
#include<cmath>
#include<queue>
#include<map>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define ll long long 
#define MAXM 60010
#define MAXN 50
#define INF 0x3f3f3f3f
//9223372036854775807
using namespace std;

int n,m,ans=INF;
int vis[MAXM][12];
int id[MAXN];
int g[MAXN][MAXN];
int state[MAXM][12];
struct node {
 int u,dis,step;//当前点,当前花费,状态 
};

bool check(int x) {
 for(int i=1;i<=n;++i) {
  if(!state[x][i])return 0;
 }
 return 1;
}

void bfs(int x) {
 queue<node> q;
 q.push({x,0,id[x]});
 vis[id[x]][x]=0;//起点入队,并且记录状态 
 while(q.size()) {
  node cur=q.front();
  q.pop();
  int u=cur.u;
  int step=cur.step;
  if(check(cur.step)) {
   ans=min(ans,cur.dis);
   continue;
  }//检查是否满足每个城市都至少拜访的了一次 ,如果成立就更新答案。 
  for(int v=1;v<=n;++v) {
   if(v==u||g[u][v]==-1)continue;
   if(state[cur.step][v]<2) {//一个点不能经过超过两次 
    int tmp=step+id[v];
    int dis=cur.dis+g[u][v];
    if(dis>=ans)continue;//如果当前的花费已经比答案要多可以直接减枝了 
    if(dis<vis[tmp][v]) {//如果当前状态来过那么只有比之前少的花费才继续往下搜索
     q.push({v,dis,tmp});
     vis[tmp][v]=dis;     
    }
   }
  } 
 }
}

void init() {
 for(int i=1;i<=10;++i) id[i]=pow(3,i-1);//预处理出每一个点拜访一次对状态数的影响 
 int tot=pow(3,10);
 for(int i=1;i<=tot;++i) {
  int tmp=i,p=0;
  while(tmp) {
   state[i][++p]=tmp%3;//代表i状态第p个城市拜访了多少次 
   tmp/=3;
  }
 }//预处理出每一个状态各个城市的拜访情况 
}

int main() {
 init();
 while(scanf("%d%d",&n,&m)!=EOF) {
  memset(vis,INF,sizeof(vis));//清空边 
  memset(g,-1,sizeof(g));//清空图 
  for(int i=1;i<=m;++i) {
   int u,v,val;
   scanf("%d%d%d",&u,&v,&val);
   if(g[u][v]==-1) {
    g[u][v]=g[v][u]=val;
   }
   else {
    g[u][v]=min(g[u][v],val);//每次只保留两点之间最短的路径 
          //如果用链式前向星会在搜索时,向队列中加入过多的无用状态 
    g[v][u]=g[u][v];
   }
  }
  ans=INF;
  for(int i=1;i<=n;++i) {
   bfs(i);//以每一个点为起点进行搜索 
  }
  if(ans==INF) printf("-1\n");
  else printf("%d\n",ans);
 }
}
//最近状态真是不好,一个水题DEBUG一天。静下心来,慢慢思考吧。 I DON'T CARE
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值