[BZOJ4774] 修路

题目链接

题意

  在一张无向图中选择一些边,使得所有点 $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;
}
View Code
原文地址:https://www.cnblogs.com/Pedesis/p/11377119.html