返回上一页

最小斯坦纳树学习笔记

让我们从一道题开始

给定一张 n 个点 m 条边的连通无向图,选定 k 个点要求这些点能互相到达. 但每条边都有一定的开通代价,第 i 条连接 aibi 的道路代价为 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上
}

例题

Luogu P6192 【模板】最小斯坦纳树

题目描述

给定一个包含 个结点和 条带权边的无向连通图 .

再给定包含 个结点的点集 ,选出 的子图 ,使得:

  1. 为连通图;

  2. 中所有边的权值和最小.

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

输入格式

第一行:三个整数 ,表示 的结点数、边数和 的大小.

接下来 行:每行三个整数 ,表示编号为 的点之间有一条权值为 的无向边.

接下来一行: 个互不相同的正整数,表示 的元素.

输出格式

第一行:一个整数,表示 中边权和的最小值.

样例 #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;
}
posted @   1Liu  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示