最小斯坦纳树 学习笔记

最小斯坦纳树

给定一张无相连通图,每条边有权值,有 \(k\) 个关键点,要求选择权值和最小的边使得关键点连通,求权值和。

类似最小生成树,但是限定了关键点就只能用指数级的复杂度解决,这里考虑类似状压 DP 的方法。

例题:P6192 【模板】最小斯坦纳树

首先最终答案显然是一个树。

所以我们设 \(f_{i,S}\) 表示以 \(i\) 为根的树,包含了关键点中 \(S\) 的这些点,\(S\) 是一个 \(01\) 串。

考虑怎么转移,分 \(i\) 的度数为 \(1\),和不为 \(1\) 两种情况。

\(1\) 则:

\[f_{i,S}+w(i,j)\rightarrow f_{j,S} \]

不为 \(1\)\(i\) 有两个子树,把两个子树合并即可:

\[f_{i,S}+f_{i,T}\rightarrow f_{i,S+T} \]

要注意在这里我们不用关心 \(i,j\) 是否是关键点而更新后面的 \(S\),我们只需在最开始把关键点 \(i\) 赋为 \(f_{i,\{i\}}=0\) 即可,然后后面的转移相当于把这些关键点合并。

由于 \(S\) 肯定是逐渐变大的,所以考虑在最外层枚举 \(S\),用之前的状态更新当前的状态,这里是 \(O(2^k)\)

考虑怎么实现,对于上面一条转移,注意我们不是只做一条边,可能是很多条边连起来,那么就做最短路,用优先队列 \(Dijkstra\) 可以做到 \(O(n\log m)\),具体地就是一开始把所有点都推进队列里,总的就是 \(O(2^k n\log m)\)

对于下面这条转移,就是一个枚举子集,而枚举子集可以做到 \(3^k\),具体来说是这样(\(j\)\(i\) 的子集):

for(int i=0;i<(1<<k);i++)
	for(int j=i;j;j=((j-1)&i))
        do something

所以这一部分的总复杂度是 \(O(3^k n)\)

注意一件事情,我们需要先做度数不为 \(1\) 的转移,再做为 \(1\) 的转移,相当于是先得到更大的 \(S\),然后才能把这个 \(S\) 扩展到更多的点;否则若更大的 \(S\) 还没合并,不是最优的,那么先扩展到更多的点也不是最优的。

最后的复杂度就是 \(O(3^kn+2^k n\log m)\)

AC 代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,K;
const int N=105,M=505;
vector<pair<int,int>> g[N];
int f[N][1<<10],bz[N];
struct arr{
	int val,num;
	arr(int _val,int _num){
		val=_val,num=_num;
	}
};
int operator<(arr x,arr y){
	return x.val>y.val;
}
int id[N];
int main(){
//	freopen("P.in","r",stdin);
	ios::sync_with_stdio(0);
	cin>>n>>m>>K;	
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back(make_pair(v,w));
		g[v].push_back(make_pair(u,w));
	}
	int X;
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=K;i++){
		int x; cin>>x;
		X=x; id[x]=i;
		f[x][1<<i-1]=0;
	}
	for(int i=1;i<(1<<K);i++){
		for(int j=i;j;j=((j-1)&i)){
			for(int k=1;k<=n;k++)
			f[k][i]=min(f[k][i],f[k][j]+f[k][i-j]);
		}
		memset(bz,0,sizeof bz);
		priority_queue<arr> q;
		for(int k=1;k<=n;k++)q.push(arr(f[k][i],k));
		while(q.size()){
			int u;
			while(q.size()&&bz[u=q.top().num])q.pop();
			if(q.empty())break;
			bz[u]=1; q.pop();
			for(auto _v:g[u]){
				int v=_v.first,co=_v.second;
				if(f[v][i]>f[u][i]+co){
					f[v][i]=f[u][i]+co;
					q.push(arr(f[v][i],v));
				}
			}
		}
	}
	cout<<f[X][(1<<K)-1];
}

相关练习

P4294 [WC2008] 游览计划

P3264 [JLOI2015] 管道连接

经过一些思考可以发现,最终答案一定是所有颜色所形成的斯坦纳树的并,于是对于每种颜色的斯坦纳树拿出来再做一次状压 DP 即可。

另外在这道题中,数据范围较大,因此 \(SPFA\) 跑得比 \(Dijkstra\) 更快。

P3638 [APIO2013] 机器人

posted @ 2024-09-04 21:28  dengchengyu  阅读(22)  评论(0编辑  收藏  举报