>Link
>Description
在一张大小为 n n n 边数为 m m m 的无向图中,要求用最小的代价联通指定的 k k k 个点。
1 ≤ n ≤ 100 , 1 ≤ m ≤ 500 , 1 ≤ k ≤ 10 , 1 ≤ u , v ≤ n , 1 ≤ w ≤ 1 0 6 1\leq n\leq 100,\ \ 1\leq m\leq 500,\ \ 1\leq k\leq 10,\ \ 1\leq u,v\leq n,\ \ 1\leq w\leq 10^6 1≤n≤100, 1≤m≤500, 1≤k≤10, 1≤u,v≤n, 1≤w≤106
>解题思路
这是一道斯坦纳树模板题。
可以很容易得出,最终以最优代价联通
k
k
k 个点的子图是一棵树(边数最少)
看到
k
k
k 小于10,可以想到状压。我们设
f
i
,
s
f_{i,s}
fi,s 为联通点集为
s
s
s,联通图(树)以
i
i
i 为根,付出的最小代价。那么就有两种转移:
① 如果
i
i
i 有多个子树,那么就把
s
s
s 分成多个子集,当做每个子树联通的点集,实际转移中分成两个
j
,
t
j,t
j,t 就可以实现
f
i
,
s
=
f
i
,
j
+
f
i
,
t
f_{i,s}=f_{i,j}+f_{i,t}
fi,s=fi,j+fi,t
② 如果
i
i
i 只有一个相邻节点
j
j
j(且
i
i
i 还是根),那么
f
i
,
s
=
f
j
,
s
+
e
i
,
j
.
w
f_{i,s}=f_{j,s} + e_{i,j}.w
fi,s=fj,s+ei,j.w
这个转移可以用
S
P
F
A
SPFA
SPFA 或
d
i
j
dij
dij 来实现
要注意的是:如果在实际情况下不是边权而是点权,那第一个转移方程还要再减去一个
a
i
,
j
a_{i,j}
ai,j,因为重复加了一次。因为这个调了差不多一个晚上T_T
>代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 5010
using namespace std;
queue<int> Q;
struct edge
{
int to, nxt, w;
} e[N * 2];
int n, m, k, f[N][N], cnt, h[N], T, p[N];
bool vis[N];
void add (int u, int v, int w)
{
e[++cnt] = (edge){v, h[u], w}; h[u] = cnt;
e[++cnt] = (edge){u, h[v], w}; h[v] = cnt;
}
void spfa (int s)
{
int u, v;
while (!Q.empty())
{
u = Q.front(), Q.pop(), vis[u] = 0;
for (int i = h[u]; i; i = e[i].nxt)
{
v = e[i].to;
if (f[u][s] + e[i].w <= f[v][s])
{
f[v][s] = f[u][s] + e[i].w;
if (!vis[v]) vis[v] = 1, Q.push (v);
}
}
}
}
int main()
{
scanf ("%d%d%d", &n, &m, &k);
int u, v, w;
for (int i = 1; i <= m; i++)
{
scanf ("%d%d%d", &u, &v, &w);
add (u, v, w);
}
memset (f, 0x7f, sizeof (f));
for (int i = 1; i <= k; i++)
{
scanf ("%d", &p[i]);
f[p[i]][1 << (i - 1)] = 0;
}
for (int s = 1; s < (1 << k); s++)
{
memset (vis, 0, sizeof (vis));
for (int i = 1; i <= n; i++)
{
for (int subs = s & (s - 1); subs; subs = s & (subs - 1))
{
f[i][s] = min (f[i][s], f[i][subs] + f[i][s ^ subs]);
}
if (f[i][s] != f[0][0])
{
Q.push (i);
vis[i] = 1;
}
}
spfa (s);
}
printf ("%d", f[p[1]][(1 << k) - 1]); //因为是树所以哪个点为根都无所谓
return 0;
}