[学习笔记]斯坦纳树

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

引自百度百科

由于网上的所有解释都不讲人话,因此我结合例题来分析:

P6192 最小斯坦纳树

给定一个包含 $n$ 个结点和 $m$ 条带权边的无向连通图 $G=(V,E)$。

再给定包含 $k$ 个结点的点集 $S$,选出 $G$ 的子图 $G^\prime=(V^\prime,E^\prime)$,使得:

1. $S\subseteq V^\prime$;

2. $G^\prime$ 为连通图;

3. $E^\prime$ 中所有边的权值和最小。

你只需要求出 $E^\prime$ 中所有边的权值和。

首先,我们要知道答案子图显然是一棵树,因为如果其上有个环,可以断开环上任意一条边,答案肯定变小。

那么考虑状压 $\operatorname{DP}$,不妨设 $dp[i][S]$ 表示以 $i$ 为根的树中包含了关键节点集合 $S$ 时的答案

分类讨论:

$\qquad\mathfrak{1:}$ $i$ 节点的度等于 $1$

$\qquad\qquad$ 也就是说只有一个节点和 $i$ 相连,不妨设其为 $j$,那么考虑用 $dp[j][S]+dis[i][j]$ 更新 $dp[i][S]$

$\qquad\qquad$ 如果 $i$ 也是一个关键点,为什么$S$ 不变呢?

$\qquad\qquad$ 这是因为作为一个连通图(理论上是森林),我们会用 $\operatorname{SPFA}$ 去反复更新做到最优,因此在整个森林里 $S$ 是相等的

$\qquad\qquad$ 简而言之,点已经被选好了,我们只是用不同的点对一个点进行松弛而已

$\qquad\mathfrak{2:}$ $i$ 节点的度大于 $1$

$\qquad\qquad$ 考虑到 $i$ 可以被划分成两个子集的并,枚举其中一个子集,用 $dp[i][S_1]+dp[i][S-S_1]$ 更新 $dp[i][S]$

$\qquad\qquad$ 时间复杂度是 $\sum\limits_{S\subseteq \{1,2,3,\cdots ,n\}}2^{\left\vert S \right\vert}=\sum\limits_{k=0}^{n}2^k\sum\limits_{S\subseteq \{1,2,3,\cdots ,n\}}[\left\vert S \right\vert =k]=\sum\limits_{k=0}^{n}2^kC_n^k=\sum\limits_{k=0}^{n}2^{n-k}C_n^k=3^n$

因此总体时间复杂度是 $\Theta(n\times 3^k+2^kmn)$

 

#include<cstdio>
#include<cstring>
#include<string>
#include<queue>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=10010,INF=1099588621776;
struct Edge{
    int pre,to,val;
}edge[WR];
int n,m,k;
int p[WR];
int head[WR],tot;
int dp[550][2010];
int dis[WR];
bool vis[WR];
queue<int>q;
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<1)+(s<<3)+ch-'0';
        ch=getchar();
    }
    return s*w;
}
void add(int u,int v,int val){
    edge[++tot].pre=head[u];
    edge[tot].to=v;
    edge[tot].val=val;
    head[u]=tot;
}
void SPFA(int S){
    while(!q.empty()){
        int u=q.front();q.pop();
        vis[u]=false;
        for(int i=head[u];i;i=edge[i].pre){
            int v=edge[i].to,val=edge[i].val;
            if(dp[v][S]>dp[u][S]+val){
                dp[v][S]=dp[u][S]+val;
                if(!vis[v]){
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
}
signed main(){
    n=read(),m=read(),k=read();
    memset(dp,0x3f,sizeof(dp));
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),val=read();
        add(u,v,val);add(v,u,val);
    }
    for(int i=1;i<=k;i++){
        p[i]=read();dp[p[i]][1<<(i-1)]=0;
    }
    for(int S=0;S<(1<<k);S++){
        for(int i=1;i<=n;i++){
            for(int S1=(S-1)&S;S1;S1=(S1-1)&S){
                dp[i][S]=min(dp[i][S],dp[i][S1]+dp[i][S^S1]);
            }
            if(dp[i][S]<=1e9) q.push(i);
        }
        SPFA(S);
    }
    int ans=INF;
    for(int i=1;i<=k;i++) ans=min(ans,dp[p[i]][(1<<k)-1]);
    printf("%lld\n",ans);
    return 0;
}
View Code

 

posted @ 2022-08-16 21:42  冬天的雨WR  阅读(131)  评论(0)    收藏  举报
Live2D