浅析SteinerTree(斯坦纳树)

来源:互联网 发布:雀神作弊软件 编辑:程序博客网 时间:2024/04/30 12:46

前几天看ZOJ的一场Monthly,里面有一道和SteinerTree很相近的一道题,之后敲了一下,顺便把SteinerTree整理一下。

首先由我们知道的东西引入:

一个图的最小生成树即这个图上所有点(设集合为G)生成的边权值和最小的树,我们可以知道这个树上任意两点是可到达的,这个可以用prime或者kruskal实现。下面,假如我们只要求G的一个真子集里面的所有点连通,那么我们发现一些边权是没有用的、可以去掉,这样,将指定点集合中的所有点连通,且总边权值和最小的生成树称谓MinimalSteinerTree(最小斯坦纳树),可以看出,最小生成树是最小斯坦纳树的一种特殊情况。

SteinerTree是组合优化学的著名问题,而且是离散数学中几个NP问题之一,可是很多离散书中提都不提.....SteinerTree对于求解数学模型,优化最小网络很有用。

求解方法:

首先,我们从prime的思想出发,如果求最小斯坦纳树,那么只要求对应点集为最小生成树即可,而对应点集的最小生成树和整体最小生成树想比肯定会出现无用点,所以先用floyd算出两点之间最短距离,之后将所有点都两两连接起来,形成一个完全图,这样就将整个图缩了一下,之后,枚举所有可能点集,对每个点集求最小生成树,取最小即可。

可是我们会发现,点集的个数是个不可知,而且每次都要做一次prime算法,所以复杂之高难以估算。

于是,我们要改进一下算,转用DP来求解。

DP转移方程:

dp[mask][i],其中是以i为根,包含mask点集的最小生成树的权值,mask作为点集可以用二进制进行状态压缩。

在得知dp[mask - 1][1...N]的情况下如何退出dp[mask][1...N]呢?

两个步骤实现:

step1:

从点集入手

a = min(dp[m1][i] + dp[m2][i]),其中m1 | m2 = mask。

m1和m2作为mask的子集,且互补。

在计算子集的时候,可有用for(int sub = mask; sum; sum = (sum - 1) & mask)来枚举子集

 

step2:

从根入手

b = min(dp[mask][j] + dp[j][i])

如果mask点集满足且根为j,那么直接填上[j][i]这条边就行了(前面拿floyd已经缩图了。)

程序中,每次都从dp[mask][1...N]中选出最小的一个dp[mask][c],按这种顺序更新就能保证结果的正确

所以,dp[mask][i] = min(a, b);


附程序(POJ3123的)

#include <iostream>#include <string>#include <map>using namespace std;int main(){int INF = 99999999, N, K, d[30][30], i, j, k, x, y, z;int dp[256][30], e[8], v[30], c, b;string s, t;while (cin >> N >> K && N){map <string, int> cityMap;for (i = 0; i < N; i++)for (j = 0; j < N; j++)d[i][j] = i == j ? 0 : INF;for (i = 0; i < N; i++){cin >> s;cityMap[s] = i;}if (K)while (cin >> s >> t >> z, x = cityMap[s],y = cityMap[t],d[x][y] = d[y][x] = min(d[y][x], z), --K);for (k = 0; k < N; k++)for (i = 0; i < N; i++)for (j = 0; j < N; j++)d[i][j] = min(d[i][j], d[i][k] + d[k][j]);for (i = 0; i < 8; i++){cin >> s;e[i] = cityMap[s];for (j = 0; j < N; j++)dp[1 << i][j] = d[j][e[i]];}for (i = 1; i < 256; i++){if (!(i & (i - 1)))continue;// step1for (k = 0; k < N; k++){dp[i][k] = INF;v[k] = 0;/*for (j = 1; j < i; j++)if ((i | j) == i)dp[i][k] = min(dp[i][k], dp[j][k] + dp[i-j][k]);*/for (int sub = i; sub; sub = (sub - 1) & i){dp[i][k] = min(dp[i][k], dp[sub][k] + dp[i - sub][k]);}}// step2for (j = 0; b = INF, j < N; j++){for (k = 0; k < N; k++)if (dp[i][k] <= b && !v[k])b = dp[i][c = k];for (k = 0, v[c] = 1; k < N; k++)dp[i][c] = min(dp[i][c], dp[i][k] + d[k][c]);}}// step3for (i = 0, b = INF; z = 0, i < 256; b = min(b, z), i++)for (j = 0; y = 0, j < 4; z += !!y * dp[y][x], j++)for (k = 0; k < 8; k += 2)if ((i >> k & 3) == j)y += 3 << k, x = e[k];cout << b << endl;}return 0;}


原创粉丝点击