最小斯坦纳树学习笔记
让我们从一道题开始
给定一张 n 个点 m 条边的连通无向图,选定 k 个点要求这些点能互相到达. 但每条边都有一定的开通代价,第 i 条连接 ai 和 bi 的道路代价为 wi ,现在为将这些点连通,求最小的代价 (n≤100,m≤500,k≤10).
引入状压 dp
发现 k 很小,所以这类问题一般可以使用状压 dp 来完成.
由于要求代价要尽可能小,所以我们不会去添加任何一条多余的边. 所以得到了一个性质:
答案所连接的图一定是树.
如何进行 dp 呢?设 dp[i][S] 表示当前连出的这个图(树)的根为 i,已连通的点集为 S.
当然,这个 S 是包含于 k 的,否则时间空间难以承受.
下面有转移:
A. ;
B. .
第一个式子可以直接枚举子集进行更新,即:
for(int S=0;S<1<<k;S++)
for(int i=1;i<=n;i++)
for(int T=S&(S-1);T;T=S&(T-1))//结论,可以枚举到S的全部子集(比如集合101就可以枚举到100、001,直到000为止)
dp[i][S]=min(dp[i][S],dp[i][T]+dp[i][S^T/*∁sT*/]);
第二个式子,虽然无法直接更新,但发现它符合三角形不等式,跑一遍最短路即可(这里用的是SPFA).
inline void spfa(int S)
{
for(int i=1;i<=n;i++)
{
dis[i]=dp[i][S];// 把上次的dp值挂到dis上
if(dis[i]!=0x3f3f3f3f3f3f3f3f) q.push(i),fl[i]=1;
}
while(!q.empty())// 标准spfa
{
int t=q.front();q.pop();fl[t]=0;
for(int i=0;i<g[t].size();i++)
{
int to=g[t][i].first;
if(dis[to]>dis[t]+g[t][i].second)
{
dis[to]=dis[t]+g[t][i].second;
if(!fl[to]) q.push(to),fl[to]=1;
}
}
}
for(int i=1;i<=n;i++) dp[i][S]=dis[i];// 把dis还原到dp上
}
例题
题目描述
给定一个包含 个结点和 条带权边的无向连通图 .
再给定包含 个结点的点集 ,选出 的子图 ,使得:
-
;
-
为连通图;
-
中所有边的权值和最小.
你只需要求出 中所有边的权值和.
输入格式
第一行:三个整数 ,表示 的结点数、边数和 的大小.
接下来 行:每行三个整数 ,表示编号为 的点之间有一条权值为 的无向边.
接下来一行: 个互不相同的正整数,表示 的元素.
输出格式
第一行:一个整数,表示 中边权和的最小值.
样例 #1
样例输入 #1
7 7 4
1 2 3
2 3 2
4 3 9
2 6 2
4 5 3
6 5 2
7 6 4
2 4 7 5
样例输出 #1
11
提示
【样例解释】
样例中给出的图如下图所示,红色点为 中的元素,红色边为 的元素,此时 中所有边的权值和为 ,达到最小值.
【数据范围】
对于 的数据,.
保证给出的无向图连通,但可能存在重边和自环.
套模板即可,注意要开 long long.
参考代码
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define rg register
#define rll rg ll
#define maxn 501
#define inf 0x3f3f3f3f3f3f3f3f
#define pll pair<ll,ll>
#define put_ putchar(' ')
#define putn putchar('\n')
using namespace std;
static inline ll read()
{
rll f=0,x=0;rg char ch=getchar();
while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^'0'),ch=getchar();
return f?-x:x;
}
static inline void write(rll x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);putchar(x%10|'0');
}
ll n,m,k,ans=inf;
vector<pll> g[maxn];
ll dis[maxn],dp[maxn][1<<11];
bool fl[maxn];
queue<ll> q;
static inline void spfa(rll S)
{
for(rll i=1;i<=n;i++)
{
dis[i]=dp[i][S];
if(dis[i]!=inf) q.push(i),fl[i]=1;
}
while(!q.empty())
{
rll t=q.front();q.pop();fl[t]=0;
for(rll i=0;i<g[t].size();i++)
{
rll to=g[t][i].first;
if(dis[to]>dis[t]+g[t][i].second)
{
dis[to]=dis[t]+g[t][i].second;
if(!fl[to]) q.push(to),fl[to]=1;
}
}
}
for(rll i=1;i<=n;i++) dp[i][S]=dis[i];
}
int main()
{
memset(dp,0x3f,sizeof(dp));n=read();m=read();k=read();
for(rll i=1,u,v,w;i<=m;i++) u=read(),v=read(),w=read(),g[u].push_back((pll) { v,w }),g[v].push_back((pll) { u,w });
for(rll i=1;i<=k;i++) dp[read()][1<<i-1]=0;
for(rll S=0;S<1<<k;S++)
{
for(rll i=1;i<=n;i++)
for(rll T=S&S-1;T;T=S&T-1) dp[i][S]=min(dp[i][S],dp[i][T]+dp[i][S^T]);
spfa(S);
}
for(rll i=1;i<=n;i++) ans=min(ans,dp[i][(1<<k)-1]);write(ans);
return 0;
}
--END--
我的博客: 𝟷𝙻𝚒𝚞
本文链接: https://www.cnblogs.com/1Liu/p/16704394.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!