题目链接:https://ac.nowcoder.com/acm/problem/16122
大意:给出一个n个点和m条边带权的无向图,给出r个旅游景点,问游玩这r个景点的最小花费是多少。
思路:r的范围很小,并且也不知道起始点和终点,所以可以枚举起点和终点。
用二进制来表示01来表示这r个景点是否已经游玩,0表示未游玩,1表示已经游玩,然后枚举所有状态,总共(1~(1<<r)-1)种情况。
定义状态:dp[mask][j] 表示当前游玩状态是mask,当前位置再点j的最小花费,现在要前往其它未游玩过的景点。
状态转移:
- 解释 :用dp[mask][j]去更新其它点,从点j去往其它未游玩过的景点。
初始化:
- 解释:自己为起点,到自己花费为0。
Code
#include <bits/stdc++.h>
#define ll long long
#define pir pair<int, int>
#define pirl pair<ll, ll>
#define debug(x) cout << #x << ":" << x << "\n"
const int mod = 1e9 + 7;
const ll ds = 1e18;
const double eps = 1e-8;
using namespace std;
const int N = 1e5 + 5;
int dp[50005][25],num[25],d[205][205];//dp[i][j] 表示当前状态为i 在j号节点
void solve() {
int n,m,r;
scanf("%d%d%d",&n,&m,&r);
for(int i = 0; i < r; i++) {
scanf("%d",&num[i]);
}
memset(d,0x3f3f3f3f,sizeof(d));
memset(dp,0x3f3f3f3f,sizeof(dp));
for(int i = 1; i <= m; i++) {
int u,v,val;
scanf("%d%d%d",&u,&v,&val);
d[u][v] = val;
d[v][u] = val;
}
for(int i = 1; i <= n; i++) d[i][i] = 0;
for(int i = 0; i < r; i++) dp[1<<i][i] = 0;
for(int k = 1; k <= n; k++) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
}
}
}
int mask = (1<<r)-1;
for(int i = 1; i <= mask; i++) {//状态
for(int j = 0; j < r; j++) {// 当前在哪个点
if(!(i&(1<<j))) continue; //如果当前在j点 那么j点一定为1
for(int k = 0; k < r; k++) {// 要去往哪个点
if(i&(1<<k)) continue; //已经走了的点不用再走
//cout << i << " " << j << " " << dp[i][j] << endl;
dp[i|(1<<k)][k] = min(dp[i|(1<<k)][k],dp[i][j]+d[num[j]][num[k]]);
//cout << dp[i|(1<<k)][k] << endl;
}
}
}
int ans = 0x3f3f3f3f;
for(int i = 0; i < r; i++) {
//cout << dp[mask][i] << endl;
ans = min(ans,dp[mask][i]);
}
printf("%d\n",ans);
}
int main() {
// int T;
// cin >> T;
// while (T--)
solve();
return 0;
}