Steiner最小生成树,NP问题,求一个图中包含某k个特定节点时的最小代价树,k较小时可以利用2^k的状压dp解决。
参考 http://endlesscount.blog.163.com/blog/static/821197872012525113427573/
最优解由多棵子树构成,可以用 f[i][sta] 表示以i为根节点,位串sta为那k个特定点与i节点的连通关系(树的形态)。
初始化为 f[i][mask[i]] = 0,这里的i遍历所有k个点,mask[i]为其连通性状态的表示。
转移:
f[i][sta] = min(f[i][sta], f[i][t | mask[i]] + f[i][(sta ^ t) | mask[i]]),其中t为sta的子集,该转移过程说明,f[i][sta]的最优解可以由合并两棵以i为根的不同形态子树得到。
通过for (int t = (sta - 1) & sta; t; t = (t - 1) & sta) 枚举子集来实现转移。
另一种转移
从i点到j点:可知 f[j][sta | mask[j]] = min(f[j][sta | mask[j]], f[i][sta] + w[i][j]),看到这个转移方程,与最短路的松弛条件何其相似!因此在枚举状态sta时,如果遇到在第一类转移之后有更新的节点(其实直接使用此时已经取到“最优解”的点即可),将其加入队列,在第一类转移全部完成之后用spfa做一次迭代更新,由于这种转移既可以从i到j,也可以从j到i,因此普通的转移是不能保证最优性的,而spfa采用迭代逼近的方式,将更新过的点再进行更新,可以保证最后每个节点都是“最短路”。
对于ZOJ WormHole Transportation 和 HDU Peach Blossom Spring这样的题,其解为一片斯坦纳生成森林,因此再使用一个dp[sta]数组来维护最优解,在spfa转移完成之后即可利用f[i][sta]来松弛dp[sta],得到当前状态的最小代价。最后由小到大枚举状态,将森林在0代价的条件下合并成“生成树”即可。在这过程中要注意用非法状态去更新合法状态可能会出现看似更优的解,但由于在这次合并中忽略了代价,故而非法状态是错误状态,需要判断一下。
HDU 4085代码
struct Edge { int v, w; Edge *nxt; } pool[E], *nowe, *adj[V]; int n, m; int k; int mask[V], end_sta; int f[V][1 << K | 1]; int dp[1 << K | 1]; int inq[V][1 << K | 1]; queue<int> que; inline void init() { nowe = pool; memset(adj, 0, sizeof (adj)); memset(f, 0x3f, sizeof (f)); memset(dp, 0x3f, sizeof (dp)); memset(mask, 0, sizeof (mask)); memset(inq, 0, sizeof (inq)); while (!que.empty()) que.pop(); } inline void add(const int &u, const int &v, const int &w) { Edge *&au = adj[u], *&av = adj[v]; nowe->v = v, nowe->w = w, nowe->nxt = au, au = nowe++; nowe->v = u, nowe->w = w, nowe->nxt = av, av = nowe++; } inline bool relax(int &a, int b) { if (a > b) return a = b, true; return false; } void spfa() { int u, st; while (!que.empty()) { u = que.front(), que.pop(); st = u & 1023, u >>= K; inq[u][st] = 0; for (Edge *e = adj[u]; e; e = e->nxt) if (relax(f[e->v][st | mask[e->v]], f[u][st] + e->w)) if ((st | mask[e->v]) == st && !inq[e->v][st]) que.push(e->v << 10 | st), inq[e->v][st] = 1; } } inline bool check(int num) { int cnt = 0; for (int i = 0; i < k; i++) if (num & (1 << i)) cnt++; for (int i = k; i < (k + k); i++) if (num & (1 << i)) cnt--; return (cnt == 0); } int main() { int T; for (scanf("%d", &T); T; T--) { scanf("%d %d %d", &n, &m, &k); init(); for (int i = 0; i < m; i++) { int u, v, w; scanf("%d %d %d", &u, &v, &w); add(--u, --v, w); } for (int i = 0; i < k; i++) { mask[i] = 1 << i; mask[n - i - 1] = 1 << (k + i); f[i][mask[i]] = 0; f[n - i - 1][mask[n - i - 1]] = 0; } end_sta = 1 << (k + k); for (int sta = 1; sta < end_sta; sta++) { for (int i = 0; i < n; i++) { if (!mask[i] || mask[i] & sta) { for (int t = (sta - 1) & sta; t; t = (t - 1) & sta) relax(f[i][sta], f[i][t | mask[i]] + f[i][(sta ^ t) | mask[i]]); if (f[i][sta] != INF) que.push(i << 10 | sta), inq[i][sta] = 1; } } spfa(); } for (int i = 0; i < n; i++) for (int sta = 1; sta < end_sta; sta++) relax(dp[sta], f[i][sta]); for (int sta = 1; sta < end_sta; sta++) if (check(sta)) { for (int t = (sta - 1) & sta; t; t = (t - 1) & sta) if (check(t)) relax(dp[sta], dp[t] + dp[sta ^ t]); } if (dp[end_sta - 1] == INF) puts("No solution"); else printf("%d\n", dp[end_sta - 1]); } return 0; }