[学习笔记]斯坦纳树

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

引自百度百科

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

P6192 最小斯坦纳树

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

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

1. SV

2. G 为连通图;

3. E 中所有边的权值和最小。

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

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

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

分类讨论:

1: i 节点的度等于 1

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

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

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

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

2: i 节点的度大于 1

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

时间复杂度是 S{1,2,3,,n}2|S|=k=0n2kS{1,2,3,,n}[|S|=k]=k=0n2kCnk=k=0n2nkCnk=3n

因此总体时间复杂度是 Θ(n×3k+2kmn)

 

复制代码
#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 @   冬天的雨WR  阅读(124)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
Live2D
点击右上角即可分享
微信分享提示