根据题意,首先使用链式前向星存图。
接着设 dp[i][s] 表示以 i 为根的一棵树,包含题目中所要求的集合 S 中所有点的最小代价。大佬的解析
我做了个算法流程图,如下:
根据上面的算法流程图,可以知道是需要枚举集合状态的,那么这里就可以使用状态压缩。用二进制的方式来表示是否结点被包含在内,0表示未包含,1表示包含。比如要包含7、8、9三个结点,可以使用三位二进制位的S来表示,S=001B(二进制表示)表示包含第一个结点7,S=010B表示包含第二个结点8,S=011B则表示包含第一和第二个结点,也就是7和8。
给出一个例子,大体的算法流程图如下所示:
代码如下:
#include <iostream>
#include <queue>
#include <cstring>
#include <climits>
#include <algorithm>
using namespace std;
const int maxn = 110, maxm = 510 * 2;
struct edge {
int to, dis, next;
}e[maxm];
int head[maxn], cnt;
int dp[maxn][1025]; //2^10 = 1024
bool vis[maxn];
int n, m, k, u, v, d, p[11];
inline void add_edge(int u, int v, int d) {
cnt++; //cnt为每条边的编号
e[cnt].to = v;
e[cnt].dis = d;
e[cnt].next = head[u];
head[u] = cnt;
}
struct node {
int pos;
int dis;
bool operator <(const node &t) const {
return dis > t.dis;
}
}temp;
priority_queue<node> que;
inline void dijkstra(int s) {
memset(vis, 0, sizeof(vis));
while (!que.empty()) {
temp = que.top();
que.pop();
int cur_pos = temp.pos;
if (vis[cur_pos]) continue;
vis[cur_pos] = 1;
for(int i = head[cur_pos]; i; i = e[i].next) {
//i表示某条边的编号
int y = e[i].to;
if (dp[y][s] > dp[cur_pos][s] + e[i].dis) {
dp[y][s] = dp[cur_pos][s] + e[i].dis;
temp.pos = y;
temp.dis = dp[y][s];
que.push(temp);
}
}
}
}
void init() {
for(int i = 0; i < maxn; i++) {
for(int j = 0; j < 1025; j++) {
dp[i][j] = INT_MAX;
}
}
}
int main() {
init();
cin >> n >> m >> k;
for(int i = 1; i <= m; i++) {
cin >> u >> v >> d;
add_edge(u, v, d);
add_edge(v, u ,d);
}
for (int i=1;i<=k;i++) {
cin >> p[i];
dp[p[i]][1<<(i-1)]=0;
}
for (int s = 1; s < (1<<k); s++) {
//枚举状态(集合s)
for (int i = 1; i <= n; i++) {
//枚举结点
for (int subs = s&(s-1); subs; subs = s&(subs-1)) {
//枚举集合s的子集(但不包含s)
dp[i][s]=min(dp[i][s],dp[i][subs]+dp[i][s^subs]);
}
if (dp[i][s] != INT_MAX) {
temp.pos = i;
temp.dis = dp[i][s];
que.push(temp);
}
}
dijkstra(s);
}
int ans = INT_MAX;
for(int i = 1; i <= k; i++) ans = min(ans, dp[p[i]][(1<<k)-1]);
cout << ans;
return 0;
}