[学习笔记]斯坦纳树
斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。
引自百度百科
由于网上的所有解释都不讲人话,因此我结合例题来分析:
给定一个包含 $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; }
本文来自博客园,作者:冬天的雨WR,转载请注明原文链接:https://www.cnblogs.com/WintersRain/p/16586402.html
为了一切不改变的理想,为了改变不理想的一切