[模板] 最小斯坦纳树

[模板] 最小斯坦纳树

也不知道干什么的

引入

给定一个无向图 \(G=(V,E)\) 以及一个包含 \(k\) 个点的点集 \(S\),要求构造一个图 \(G'\),使得:

  1. \(S\sub V'\)
  2. \(G'\) 为连通图;
  3. \(E'\) 中所有边的边权和最小。

\(n\leq 100,m\leq 500,k\leq 10\)

最小化图的边权和。

解法

因为 \(k\) 比较小,显然是可以状压的。

可以考虑将原图中的点不断合并的过程,并转化为有根树来求解。

设计状态为 \(f_{S,i}\) 表示当前生成的图中包含了 \(S\) 中的关键点,当前扩展到了 \(i\) 号结点的最小边权和。

考虑两种转移:

  1. 拓展根操作

\[f_{S,i}->f_{S,j} \]

对应到图上就是向上拓展了一个单位,显然贪心的来选是需要最短路径的,因此一边 \(dijstra\) 一边扩展。

  1. 合并操作

\[f_{S1,i}+f_{S2,i}->f_{S,i}\ (S=S1+S2) \]

可以通过枚举子集来实现。

注意:需要先进行合并,再在合并后的基础上进行扩展操作。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#include <queue>
const int maxn = 100 + 10 , maxm = 500 + 10;
int head[maxn],cnt=0;
struct edge{
	int to,nxt,w;
}e[maxm<<1];
inline void link(int u,int v,int w){
	e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;e[cnt].w=w;
}
int f[(1<<10)+5][maxn];
int n,m,k,p[maxn];
bool vis[maxn];
#define Pair pair<int,int>
#define mp make_pair
priority_queue<Pair,vector<Pair>,greater<Pair> > q;
void dij(int s){
	memset(vis,false,sizeof vis);
	while(q.size()){
		int u=q.top().second;q.pop();
		if(vis[u])continue;
		vis[u]=true;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(!vis[v] && f[s][v]>f[s][u]+e[i].w){
				f[s][v]=f[s][u]+e[i].w;
				q.push(mp(f[s][v],v));
			}
		}
	}
}
#define read() read<int>()
int main(){
	n=read();m=read();k=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read(),w=read();link(u,v,w);link(v,u,w);
	}
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=k;i++){
		p[i]=read();
		f[1<<i-1][p[i]]=0;
	}
	for(int s=0;s<(1<<k);s++){
		for(int i=1;i<=n;i++){
			for(int s0=(s-1)&s;s0;s0=(s0-1)&s){
				f[s][i]=min(f[s][i],f[s0][i]+f[s^s0][i]);
			}
			if(f[s][i]!=0x3f3f3f3f)q.push(mp(f[s][i],i));
		}
		dij(s);
	}
	printf("%d\n",f[(1<<k)-1][p[1]]);
	return 0;
}
posted @ 2021-08-31 21:58  ¶凉笙  阅读(46)  评论(0编辑  收藏  举报