0
点赞
收藏
分享

微信扫一扫

36-《茅膏菜》:自然奇境中的捕虫艺术家

梅梅的时光 2024-08-10 阅读 27

最小斯坦纳树打印方案

我们以求解“最小边权和”斯坦纳树为例进行演示。

【前情回顾】

动态规划求解最小斯坦纳树的过程,主要依靠两个步骤:合并子树强行连通

除了最初始的 d p [ 1 < < ( i − 1 ) ] [ t e r m i n a l [ i ] ] dp[1<<(i-1)][terminal[i]] dp[1<<(i1)][terminal[i]]之外,其余的所有 d p dp dp 状态都是靠合并子树 + 强行连通产生的。

如何去求一个 d p [ S ] [ t e r m i n a l [ i ] ] dp[S][terminal[i]] dp[S][terminal[i]] 所表示的具体的最小斯坦纳树?只要遵循下面步骤:

1.求出 d p [ S ] [ t e r m i n a l [ i ] ] dp[S][terminal[i]] dp[S][terminal[i]] 是被谁转移的? 这个和 D i j k s t r a Dijkstra Dijkstra最短路时记录前驱结点相同。对于每个结点 i , i ∈ V i,i\in V i,iV,我们需要在强行连通操作中记录 i i i 是被谁转移的?将答案存在 f a [ S ] [ i ] fa[S][i] fa[S][i] 中。

if (dp[s][v] > dp[s][u] + w(u,v)) {
     fa[s][v] = u;
     q.push({dp[s][v],v});
}

我们一直回溯下去(假设 v v v 是由 u u u 转移而来, u u u 是由 t t t 转移而来, ⋯ \cdots ,最终一定存在一个点 x x x d p [ S ] [ x ] dp[S][x] dp[S][x] 一定是由其自身的子树合并而来,即 f a [ S ] [ x ] = x fa[S][x]=x fa[S][x]=x。)当回溯到由自身的子树合并而来的值的时候,我们终止循环。

int v = terminal[1];
while (fa[S][v] != v) { 
     v = fa[S][v];
     ans.push_back(v); //ans 是最终的最小斯坦纳树点集
}

接下来怎么操作?现在 v v v 已经是由自身的子树合并而来的,所以接下来我们要在合并子树操作中记录前驱值(子树所包含的集合)

记录 d p [ S ] [ v ] dp[S][v] dp[S][v] 合并了自身的哪两个子树?

m e r g e [ S ] [ v ] [ 0 ] = S ′ , m e r g e [ S ] [ v ] [ 1 ] = S − S ′ merge[S][v][0]=S',merge[S][v][1]=S-S' merge[S][v][0]=S,merge[S][v][1]=SS(用两个数组记录 d p [ S ] [ v ] dp[S][v] dp[S][v] 是合并了哪两个子树 d p [ S ′ ] [ v ] , d p [ S − S ′ ] [ v ] dp[S'][v],dp[S-S'][v] dp[S][v],dp[SS][v])。为了方便起见通常令 m e r g e [ S ] [ v ] [ 0 ] merge[S][v][0] merge[S][v][0] 存较小的集合, m e r g e [ S ] [ v ] [ 1 ] merge[S][v][1] merge[S][v][1] 存较大的集合。

for (int t = s; t != 0; t = (t - 1) & s) {
     for (int i = 0; i < N; i++) {
          if(dp[s][i] > dp[t][i] + dp[s ^ t][i]) {
               dp[s][i] = dp[t][i] + dp[s ^ t][i];
               merge[s][i][0] = min(t, s ^ t);
               merge[s][i][1] = max(t, s ^ t);
          }
     }
}

接下来,只要求解 d p [ S ′ ] [ v ] dp[S'][v] dp[S][v] d p [ S − S ′ ] [ v ] dp[S-S'][v] dp[SS][v] 的前驱结点即可。不难发现这是一个递归的过程,而递归的终点就是 我们递归到了初始化时的值。

//以任意的S中的一个点为起点。
int v = terminal[1];
map<int,int> ans; //用集合去重
work(S,v);
----------------------------------------
//写一个递归函数
void work(int S,int v) {
     while (fa[S][v] != v) {
          ans[v] = 1;
          v = fa[S][v];
     }
     int cnt = 0;
     for (int i = 0; i < k; i++) {
         if (S & (1 << i)) {
             cnt++;
         }
     }
     if (cnt == 1) return; //强行连通操作完了之后,如果|S|是1,说明已经到了初始值,直接退出循环
     int mask1 = merge[S][v][0], mask2 = merge[S][v][1];
     work(mask1, v); work(mask2,v);
}
-----------------------------------------
//最终答案就是map中的点。
【模版】
#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
const int K = 11;
const int N = 2000;
vector<PII> g[N]; //邻接表存图
int n, m, k;
map<int,int> ans;
int terminal[K], fa[1 << K][N], Merge[1 << K][N][2];
int bnt = 0;
void work(int S,int v) {
     bnt++;
     if(bnt > 1000) return;
     
     while (fa[S][v] != v) {
          ans[v] = 1; 
          v = fa[S][v];
          ans[v] = 1; 
     }
     
     //强行连通操作完了之后,如果|S|是1,说明已经到了初始值,直接退出循环
     int cnt = 0;
     for (int i = 0; i < k; i++) if (S & (1 << i)) cnt++;
     if (cnt == 1) return;

     int mask1 = Merge[S][v][0], mask2 = Merge[S][v][1];
     work(mask1, v); work(mask2,v);
}


void add (int u, int v, int w) { g[u].push_back({v,w}); }

void printSteinerTree(){
    work((1 << k) - 1, terminal[1]);
    for (auto i : ans) cout << i.first << ' ';
    cout << endl;
}

void slove () {
    cin >> n >> k >> m;

    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        add (u, v, w), add(v, u, w);
    }

    vector<vector<int>> dp(1 << k, vector<int>(n + 1, INF));
    for (int i = 1; i <= k; i++) {
        cin >> terminal[i];
        dp[1 << (i - 1)][terminal[i]] = 0;
    }

    for (int S = 1; S < (1 << k); S++) {
        for (int t = S; t; t = (t - 1) & S ) {
            for (int u = 1; u <= n; u++) {
               if(dp[S][u] > dp[t][u] + dp[S ^ t][u]) {
                       dp[S][u] = dp[t][u] + dp[S ^ t][u];
                       Merge[S][u][0] = min(t, S ^ t);
                       Merge[S][u][1] = max(t, S ^ t);
                }
            }
        }

        priority_queue<PII, vector<PII>, greater<PII>> q;

        for (int i = 1; i <= n; i++) {
            fa[S][i] = i; // 初始化 i是由自身转移而来的
            if (dp[S][i] != INF) q.push({dp[S][i], i});
        }

        while (q.size()) {
            auto [d,u] = q.top();
            q.pop();
            if (d != dp[S][u]) continue;
            for (auto [v,w] : g[u]) {
                if (dp[S][v] > dp[S][u] + w) {
                    dp[S][v] = dp[S][u] + w;
                    fa[S][v] = u;
                    q.push({dp[S][v], v});
                }
            }
        }
    }
    
    printSteinerTree();
    cout << dp[(1 << k) - 1][terminal[1]] << endl;

}

signed main () {
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    slove();
}
举报

相关推荐

0 条评论