算法学习:斯坦纳树

算法学习:斯坦纳树

引例

bzoj4774: 修路

定义

斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。——百度百科

求解

斯坦纳树是一个NP问题,当斯坦纳树中的点很少的时候可以采用状压DP解决。
1. f[s][i] f [ s ] [ i ] 表示连通状态为s,以i为根的最小代价
2. g[s] g [ s ] 表示连通状态为s的最小代价

两重Dp
首先求解f,枚举子集得到 f[s][i]=minin{f[t][i]+f[st][i]} f [ s ] [ i ] = min i n { f [ t ] [ i ] + f [ s − t ] [ i ] }
我们考虑相同s的不同 f[s][i] f [ s ] [ i ] ,容易得到 f[s][i]=minj=1,jin{f[s][j]+e[i][j]} f [ s ] [ i ] = min j = 1 , j ≠ i n { f [ s ] [ j ] + e [ i ] [ j ] }
这是一个有后效性的动规,但是满足三角不等式和松弛法则,可以采用spfa处理。
然后求解g, g[s]=mininf[s][i] g [ s ] = min i n f [ s ] [ i ]
这就是求解斯坦纳树的一般方法。

时间复杂度

O(n3n+kE2n) O ( n ⋅ 3 n + k E 2 n ) 3n 3 n 是枚举子集的复杂度,k是spfa的常数。

引例分析

本题基本是裸的斯坦纳树,只不过不联通的点可以不联通,求解g之后枚举子集即可。总复杂度不变

代码

#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int N = 1e4 + 10, M = 2e4 + 10, inf = 0x3f3f3f3f;
int read() {
    char ch = getchar(); int x = 0, f = 1;
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) - '0' + ch; ch = getchar();}
    return x * f;
}
int to[M], nxt[M], w[M], pre[N], q[N], f[260][N], g[260], bin[10], top, L, R, n, m, d;
bool vis[N];
void add(int u, int v, int ww) {to[++top] = v; nxt[top] = pre[u]; pre[u] = top; w[top] = ww;}
void adds(int u, int v, int w) {add(u, v, w); add(v, u, w);}
int Move(int &x) {return x == n ? x = 0 : ++x;}
void Spfa(int *F) {
    for(int u;L != R;) {
        vis[u = q[Move(L)]] = false;
        for(int i = pre[u]; i; i = nxt[i])
        if(F[to[i]] > F[u] + w[i]) {
            F[to[i]] = F[u] + w[i];
            if(!vis[to[i]]) vis[q[Move(R)] = to[i]] = true;
        }
    }
}
bool check(int s) {return (s & (bin[d] - 1)) == (s >> d);}
int main() {
    bin[0] = 1; for(int i = 1;i <= 8; ++i) bin[i] = bin[i - 1] << 1;
    n = read(); m = read(); d = read();
    while(m--) {
        int u = read(), v = read(), w = read();
        adds(u, v, w);
    }
    memset(f, 0x3f, sizeof(f)); memset(g, 0x3f, sizeof(g));
    for(int i = 1;i <= d; ++i) f[bin[i - 1]][i] = f[bin[d + i - 1]][n - i + 1] = 0;
    int S = bin[d << 1];
    for(int s = 0;s < S; ++s) {
        L = 0, R = 0;
        for(int i = 1;i <= n; ++i) {
            for(int t = s & (s - 1); t; t = (t - 1) & s)
                f[s][i] = min(f[s][i], f[t][i] + f[s ^ t][i]);
            if(f[s][i] != inf) vis[q[Move(R)] = i] = true;
        }
        Spfa(f[s]);
        for(int i = 1;i <= n; ++i) g[s] = min(g[s], f[s][i]);
    }
    for(int s = 0;s < S; ++s) 
        for(int t = (s - 1) & s; t; t = (t - 1) & s)
        if(check(t) && check(t ^ s)) 
            g[s] = min(g[s], g[t] + g[t ^ s]);
    printf("%d\n", g[S - 1] == inf ? -1 : g[S - 1]);
    return 0;
}


你可能感兴趣的:(#,动态规划)