问题描述

​最小斯坦纳树[模板]​

给 定 n 个 点 m 条 边 , 再 给 你 包 含 k 个 节 点 的 点 集 s 给定n个点m条边,再给你包含k个节点的点集s 给定n个点m条边,再给你包含k个节点的点集s

要 求 从 图 中 选 出 一 个 权 值 和 最 小 的 连 通 子 图 , 包 含 点 集 s 要求从图中选出一个权值和最小的连通子图,包含点集s 要求从图中选出一个权值和最小的连通子图,包含点集s



结论

选出来的子图是一颗树,因为不存在环

假如存在环,去掉环上任意一点不改变连通性,且权值更小

那 么 定 义 d p [ i ] [ j ] 为 以 i 为 根 的 子 树 包 含 s 点 集 状 态 为 j 的 最 小 代 价 那么定义dp[i][j]为以i为根的子树包含s点集状态为j的最小代价 那么定义dp[i][j]为以i为根的子树包含s点集状态为j的最小代价

当 i 只 有 1 个 儿 子 , 那 么 令 v 是 i 的 儿 子 当i只有1个儿子,那么令v是i的儿子 当i只有1个儿子,那么令v是i的儿子

d p [ i ] [ j ] = d p [ v ] [ j ] + ( i 到 v 的 边 代 价 ) dp[i][j]=dp[v][j]+(i到v的边代价) dp[i][j]=dp[v][j]+(i到v的边代价)

观 察 到 这 就 是 最 短 路 的 转 移 , 所 以 用 s p f a 来 实 现 观察到这就是最短路的转移,所以用spfa来实现 观察到这就是最短路的转移,所以用spfa来实现

当 i 不 只 有 1 个 儿 子 , 那 么 枚 举 j 的 子 集 为 s 当i不只有1个儿子,那么枚举j的子集为s 当i不只有1个儿子,那么枚举j的子集为s

其 中 枚 举 子 集 是 从 小 到 大 枚 举 其中枚举子集是从小到大枚举 其中枚举子集是从小到大枚举

d p [ i ] [ j ] = d p [ i ] [ s ] + d p [ i ] [ j − s ] dp[i][j]=dp[i][s]+dp[i][j-s] dp[i][j]=dp[i][s]+dp[i][j−s]

枚举子集可以用这个神奇的for循环来完成

for(int sub=s&(s-1);sub;sub=s&(sub-1) )//从小到大枚举二进制s的子集sub
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
struct edge{
int to,w,nxt;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int w){
d[++cnt]=(edge){v,w,head[u]},head[u]=cnt;
}
int n,m,k,dp[109][1<<12],vis[209],p[209];
queue<int>q;
void spfa(int s)
{
while( !q.empty() )
{
int u=q.front(); q.pop();
vis[u]=0;
for(int i=head[u];i;i=d[i].nxt )
{
int v=d[i].to;
if( dp[v][s]>dp[u][s]+d[i].w )//最短路
{
dp[v][s]=dp[u][s]+d[i].w;
//这是假设u只有一个儿子v,才可以这么转移
if( !vis[v] ) q.push( v ),vis[v]=1;
}
}
}
}
int main()
{
cin >> n >> m >> k;
memset(dp,127,sizeof(dp) );
int inf=dp[0][0];
for(int i=1;i<=m;i++)
{
int l,r,w;
cin >> l >> r >> w;
add(l,r,w); add(r,l,w);
}
for(int i=1;i<=k;i++ )
{
cin >> p[i];
dp[ p[i] ][1<<(i-1) ]=0;
}
for(int s=1;s<(1<<k);s++)
{
for(int i=1;i<=n;i++)//枚举根节点
for(int sub=s&(s-1);sub;sub=s&(sub-1) )
dp[i][s]=min( dp[i][s],dp[i][sub]+dp[i][s^sub] );
for(int i=1;i<=n;i++)
if( dp[i][s]!=inf ) q.push(i),vis[i]=1;
spfa(s);
}
cout << dp[ p[1] ][(1<<k)-1];
}