Loading

最小斯坦纳树

模拟赛遇到又爆零了。

Template&题意

正文

给定 \(G=(V,E)\) 和点集 \(S\),让你求一个连通块包含 \(S\) 内所有点且边权最小。

首先最后的连通块一定是一棵树,证明显然。

所以我们不妨钦定一个根,考虑dp。设 \(f[i,s]\) 代表以 \(i\) 为根,考虑 \(s\) 里的点形成一个连通块(虽然是连通块,但是因为保证了最小所以是棵树)。

通过两种方法转移:

  1. 假设根的度数为 \(1\),那么可以从别的根转移过来 \(f[i,s]=\min f[j,s]+w(j,i)]\)

  2. 考虑合并 \(i\) 的两的部分,\(f[i,s]=\min f[i,s]+f[i,s-t]\)

对于第二种操作我们枚举子集,所以要求我们从小到大枚举。

对于第一种操作很像最短路的松弛操作,所以直接跑最短路。

时间复杂度 \(O(3^{\mid S\mid}\mid V\mid^2+2^{\mid S\mid}\mid V\mid\mid E\mid)\)

卡常

一些卡常技巧可以让你跑 \(9e8\)

首先让 dp 数组访问连续,也就是第一维是 \(s\),第二维是 \(i\),其次用 spfa 可以让平均复杂度降低。

Code

#include<bits/stdc++.h>
#define pii std::pair<int,int>
namespace _name{
	const int maxn=200,mod=998244353,inf=0x3f3f3f3f;
	template<typename T>
	inline void read(T &x){
		T flag=1;
		char ch=getchar();
		for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1;
		for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
		x*=flag;
	}
	inline void cmax(int &a,int b){if(b>a)a=b;}
	inline void cmin(int &a,int b){if(b<a)a=b;}
}using namespace _name;
int n,m,k,f[1<<14][200];
std::vector<pii>vec[maxn];
std::queue<int>q;
bool vis[maxn];
void spfa(int s){
	for(int i=0;i<n;i++)if(f[s][i]!=inf)q.emplace(i),vis[i]=1;
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;
		for(auto [v,w]:vec[u]){
			if(f[s][u]+w<f[s][v]){
				f[s][v]=f[s][u]+w;
				if(!vis[v])q.emplace(v),vis[v]=1;
			}
		}
	}
}
int main(){
	read(n);read(m);read(k);
	for(int i=1,u,v,w;i<=m;i++){
		read(u);read(v);read(w);u--;v--;
		vec[u].emplace_back(v,w);vec[v].emplace_back(u,w);
	}
	memset(f,0x3f,sizeof(f));
	for(int i=0,x;i<k;i++){
		read(x);x--;
		f[1<<i][x]=0;
	}
	for(int s=1;s<(1<<k);s++){
		for(int t=s&(s-1);t;t=(t-1)&s){
			if(t>(s^t)){
				for(int i=0;i<n;i++)cmin(f[s][i],f[t][i]+f[s^t][i]);
			}
		}
		spfa(s);
	}
	int ans=inf;
	for(int i=0;i<n;i++)ans=std::min(ans,f[(1<<k)-1][i]);
	printf("%d\n",ans);
	return 0;
}

Reference

https://www.luogu.com.cn/blog/ix-35/solution-p6192

https://www.luogu.com.cn/blog/xyf007/solution-p6192

posted @ 2022-06-05 20:48  Semsue  阅读(38)  评论(0编辑  收藏  举报
Title