题目描述 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