Travelling
题意: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;
}