题意
在一张无向图中选择一些边,使得所有点 $i ; (1 leq i leq d)$ 和点 $n-i+1$ 联通,求边权和最小值。
数据范围
$1 leq D leq 4$,$2D leq N leq 10^4$,$0 leq M leq 10^4$
分析
这是一道最小斯坦纳树的模板题
与最小生成树不同,最小斯坦纳树只需要使给定的点集中的点连通,最小生成树是它的一种特殊情况
通常我们用 $dp$ 求解,设 $f[i][s]$ 表示以 $i$ 为根,连通的点集为 $s$ 时生成树的最小边权,$g[s]$ 表示连通的点集为 $s$ 时生成树/森林的最小边权
对于 $f$ 数组有两个转移方程 $$f[i][s]=min_{k subsetneq s}{f[i][k]+f[i][s-k]}$$ $$f[i][s]=min{f[j][s]+w[j][i]}$$
其中后者常用最短路转移
然后 $g$ 数组初始化为 $g[s]=min_{1 leq i leq n}{f[i][s]}$
显然有状态转移方程 $g[s]=min_{k subsetneq s}{g[k]+g[s-k]}$
其中点集 $k$ 和 $s-k$ 必须分别保证点集内对应点连通,因为在合并点集时两点集不一定相连
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <queue> #include <set> using namespace std; #define ll long long #define inf 0x3f3f3f3f #define N 10005 #define D 9 int n, m, d, tot; int f[N][1 << D], g[1 << D], vis[N]; int to[N << 1], len[N << 1], nxt[N << 1], head[N]; queue<int> q; void add(int u, int v, int w) { to[++tot] = v; len[tot] = w; nxt[tot] = head[u]; head[u] = tot; } void spfa(int s) { while (!q.empty()) { int x = q.front(); q.pop(); vis[x] = 0; for (int i = head[x]; i; i = nxt[i]) if (f[to[i]][s] > f[x][s] + len[i]) { f[to[i]][s] = f[x][s] + len[i]; if (!vis[to[i]]) vis[to[i]] = 1, q.push(to[i]); } } } bool check(int s) { return (s & ((1 << d) - 1)) == (s >> d); } int main() { scanf("%d%d%d", &n, &m, &d); for (int i = 1; i <= m; i++) { int u, v, w; scanf("%d%d%d", &u, &v, &w); add(u, v, w); add(v, u, w); } memset(f, 0x3f, sizeof f); memset(g, 0x3f, sizeof g); for (int i = 1; i <= d; i++) f[i][1 << i - 1] = f[n - i + 1][1 << d + i - 1] = 0; int all = 1 << (d << 1); for (int s = 0; s < all; s++) { for (int i = 1; i <= n; i++) { for (int k = s & (s - 1); k; k = (k - 1) & s) f[i][s] = min(f[i][s], f[i][k] + f[i][s ^ k]); if (f[i][s] < inf) vis[i] = 1, q.push(i); } spfa(s); for (int i = 1; i <= n; i++) g[s] = min(g[s], f[i][s]); } for (int s = 0; s < all; s++) for (int k = s & (s - 1); k; k = (k - 1) & s) if (check(k) && check(s ^ k)) g[s] = min(g[s], g[k] + g[s ^ k]); printf("%d ", g[all - 1] != inf ? g[all - 1] : -1); return 0; }