题目链接:Travelling HDU - 3001
===================================================
Travelling
Time Limit: 3000MS
Memory Limit: 32768 kB
Description
After coding so many days,Mr Acmer wants to have a good rest.So travelling is the best choice!He has decided to visit n cities(he insists on seeing all the cities!And he does not mind which city being his start station because superman can bring him to any city at first but only once.), and of course there are m roads here,following a fee as usual.But Mr Acmer gets bored so easily that he doesn’t want to visit a city more than twice!And he is so mean that he wants to minimize the total fee!He is lazy you see.So he turns to you for help.
Input
There are several test cases,the first line is two intergers n(1<=n<=10) and m,which means he needs to visit n cities and there are m roads he can choose,then m lines follow,each line will include three intergers a,b and c(1<=a,b<=n),means there is a road between a and b and the cost is of course c.Input to the End Of File.
Output
Output the minimum fee that he should pay,or -1 if he can’t find such a route.
Sample Input
2 1
1 2 100
3 2
1 2 40
2 3 50
3 3
1 2 3
1 3 4
2 3 10
Sample Output
100
90
7
===================================================
算法:3进制状态压缩+dp/bfs
难点:
- 题意理解:
But Mr Acmer gets bored so easily that he doesn’t want to visit a city more than twice!
由于这里我看太快了,意思为不能。。2次,误解为只能经过一次,然后就感觉简单,dfs+剪枝然后一直wrong。
后面看了看,原来是不能超过2次。所以状态为0,1,2.即经过的次数。用3进制进行状态压缩。
愿意就是acmer可以为了达到经过所有地点并且路费最低,允许他经过一个点不超过2次。
- 在于3进制预处理。枚举所有可能的状态,然后用数组存储各种状态下点的经过数。
- 状态最优更新:
dp[i][j]: i为3进制状态,j为最后的连接点。
1.如果状态i,检测出所有点是否经过,然后进行答案更新
2.通过遍历点j的边进行向下寻找符合进入的点,进行dp数组更新
===================================================
1.bfs(更加易懂)
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
int n,m,ans;
int path[11][11];
int state[65432][11],three[11];
//three[i]表示3的i次方,state[i][j]表示3进制状态中j位的情况(情况分别为0,1,2,代表一个点经过数)
int dp[65432][11];
//dp[i][j]3进制状态下情况以j点作为最后连接点的最优即最少代价
//(状态下一步:找经过数为0或1的点,然后进行向下添加状态,最优则更新;)
void init(){//
three[0] = 1;
for(int i=1;i<11;i++) three[i] = three[i-1]*3;
for(int i=0;i<three[10];i++){
int tmp = i;
for(int j=0;j<10;j++) state[i][j] = tmp%3,tmp/=3;
}
}
struct node{
int stat,now,free;
node (int a,int b,int c):stat(a),now(b),free(c) {}
bool check(){
for(int i=0;i<n;i++) if(state[stat][i]==0) return false;
return true;
}
};
void bfs(int x){
queue<node> q;
dp[three[x]][x] = 0;
q.push(node(three[x],x,0));
while(!q.empty()){
node p = q.front();q.pop();
if(p.check()) {ans = min(ans,p.free);continue;}
if(p.free >= ans) continue;//剪枝
for(int i=0;i<n;i++){
if(p.now==i || state[p.stat][i]>1 || path[p.now][i]==INF) continue;
int cost = p.free + path[p.now][i];
if(cost >= ans) continue;//剪枝
if(dp[p.stat+three[i]][i]!=INF&&dp[p.stat+three[i]][i]<=cost) continue;
//这里dp函数作用在于判重并且是否最优,没可能最优肯定不能入队
dp[p.stat+three[i]][i] = cost;
q.push(node(p.stat+three[i],i,cost));
}
}
}
void solve(){
memset(dp,INF,sizeof dp);
for(int i=0;i<n;i++) bfs(i);
}
int main()
{
init();
while(cin>>n>>m){
memset(path,INF,sizeof path);
for(int i=0;i<m;i++){int a,b,c;cin>>a>>b>>c;path[a-1][b-1] = min(path[a-1][b-1],c);path[b-1][a-1] = path[a-1][b-1];}
ans = INF;
solve();
if(ans==INF) puts("-1");
else cout<<ans<<endl;
}
return 0;
}
===================================================
2.状态dp(其实与bfs无异,通过3循环遍历,一循环状态i,二循环尾点j,三循环进行下一连接点k查找)
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
int n,m,ans;
int path[11][11];
int state[65432][11],three[11];
//three[i]表示3的i次方,state[i][j]表示3进制状态中j位的情况(情况分别为0,1,2,代表一个点经过数)
int dp[65432][11];
//dp[i][j]3进制状态下情况以j点作为最后连接点的最优即最少代价
//(状态下一步:找连接数为0或1的点,然后进行向下添加状态,最优则更新;)
void init(){//
three[0] = 1;
for(int i=1;i<11;i++) three[i] = three[i-1]*3;
for(int i=0;i<three[10];i++){
int tmp = i;
for(int j=0;j<10;j++) state[i][j] = tmp%3,tmp/=3;
}
}
void solve(){
memset(dp,INF,sizeof dp);
//点自己到自己代价为0
for(int i=0;i<n;i++) dp[three[i]][i] = 0;
//从状态0开始向后更新,后面状态无法影响前面状态,
//所以对于全联通的状态进行0~n-1结尾进行最终答案更新就行了
for(int i=0;i<three[n];i++){
bool flag = true;
for(int j=0;j<n;j++){
if(state[i][j]==0) {flag = false;continue;}
//if(state[i][j]==2) continue;
for(int k=0;k<n;k++){
if(path[j][k]==INF||k==j||state[i][k]==2) continue;
if(dp[i+three[k]][k] <= dp[i][j]+path[j][k]) continue;
dp[i+three[k]][k] = dp[i][j]+path[j][k];
}
}
if(flag) {for(int j=0;j<n;j++) ans = min(ans,dp[i][j]);}
}
}
int main()
{
init();
while(cin>>n>>m){
memset(path,INF,sizeof path);
for(int i=0;i<m;i++){int a,b,c;cin>>a>>b>>c;path[a-1][b-1] = min(path[a-1][b-1],c);path[b-1][a-1] = path[a-1][b-1];}
ans = INF;
solve();
if(ans==INF) puts("-1");
else cout<<ans<<endl;
}
return 0;
}