斯坦纳树 Steiner Tree

更新日志 2025/06/23:开工。

问题

对于经典的最小斯坦纳树问题,给出一个无向图以及一个点集,求包含这个点集的边权和最小的生成图。

思路

首先生成的肯定是一棵树,这是易证的。

令给出的点集中的点为关键点,考虑状压关键点。

\(f_{i,s}\) 表示以 \(i\) 为根(注意不一定要是关键点)包含集合 \(s\) 的关键点的最小边权和。

考虑枚举关键点集合,然后关键算法有两步:

  • 在当前集合所有子集均已计算完毕的情况下,通过“合并子树”操作更新一个当前集合的答案。
    更通俗一些,就是对于以每个点为根的情况,枚举当前集合的子集 \(t\),然后尝试使用 \(f_{x,t}+f_{x,s-t}\) 更新 \(f_{x,s}\)
    这个的意义是很显然的。
  • 但上述操作可能会出现某些边选重复的情况,因此我们在此之后还要松弛整个 \(f\)。用 SPFA 跑即可,这不是复杂度瓶颈。
    最后答案即为 \(f_{x,S}\)\(S\) 为给出点集,\(x\) 可以为任意关键点,答案显然都是一样的。

模板

const int N=105,K=10;

int n,k;
vec<pii> G[N];
int p[K],f[N][1<<K];
queue<int> q;
bool inq[N];
void spfa(int s){
	while(!q.empty()){
		int x=q.front();q.pop();inq[x]=0;
		for(auto e:G[x]){
			int y=e.fir,v=e.sec;
			if(f[y][s]>f[x][s]+v){
				f[y][s]=f[x][s]+v;
				if(!inq[y])q.push(y),inq[y]=1;
			}
		}
	}
}
int solve(){
	repl(i,0,n)repl(j,0,1<<k)f[i][j]=inf;
	repl(i,0,k)f[p[i]][1<<i]=0;
	repl(s,1,1<<k){
		repl(i,0,n){
			for(int t=s&s-1;t;t=s&t-1)chmin(f[i][s],f[i][t]+f[i][s^t]);
			if(f[i][s]<inf)q.push(i),inq[i]=1;
		}
		spfa(s);
	}
	return f[p[0]][(1<<k)-1];
}

常见问题

  1. 点权
    简单的,关键点的 \(f\) 初始化为自己的点权,边权加上另一侧点权即可。注意合并子树时根节点会被算两边,要减去一遍点权。
    for(int t=s&s-1;t;t=s&t-1)chmin(f[i][s],f[i][t]+f[i][s^t]-a[i]);
    
  2. 方案
    简单的,暴力递归状态 \(x,s\) 即可,找到一种方案就转移。为了防止边权为 \(0\) 在最短路策略上来回跑导致超时的情况建议记录一个最短路 \(lst\)。对于合并子集的方案,找到来源子集后(暴力枚举去找)递归两个子集即可。
    比如:
    bool vis[N];
    void dfs(int x,int s){
    	vis[x]=1;
    	if(~lst[x][s])dfs(lst[x][s],s);
    	for(int t=s&s-1;t;t=s&t-1)if(f[x][s]==f[x][t]+f[x][s^t]-a[x])return dfs(x,t),dfs(x,s^t);
    }
    
  3. 最小斯坦纳森林
    也就是给出多个点集,要求各个点集各自连通。
    先正常跑一边斯坦纳树,然后状压 DP:
    \(g_s\) 表示包含特殊点点集 \(s\) 的生成树最小答案。转移是简单的,枚举子集即可,难点在于初始化。
    对于一个集合 \(s\),如果对于每一个给出点集,要么全在这个集合中,要么全不在这个集合中,那么这就是一棵合法的生成树,更新 \(g_x\) 为最小的 \(f_{x,s}\)。否则该集合不合法,不能初始化,只能转移而来。
    repl(s,1,1<<k){
    	g[s]=inf;
    	if([&](){repl(i,0,10)if(col[i]^col[i]&s&&col[i]&s)return 0;return 1;}())
    		repl(j,0,n)chmin(g[s],f[j][s]);
    	for(int t=s&s-1;t;t=s&t-1)chmin(g[s],g[t]+g[s^t]);
    }
    

例题

前言

下面所有代码都把缺省源也一块粘上了。。可能略微影响观感。

代码中存在一定冗余,不建议复制作为模板使用。

洛谷模板题

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template<typename Type> using vec=vector<Type>;
template<typename Type> using grheap=priority_queue<Type>;
template<typename Type> using lrheap=priority_queue<Type,vector<Type>,greater<Type>>;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define repl(i,x,y) for(int i=(x);i<(y);++i)
#define ReadFile(f) freopen(#f".in","r",stdin);freopen(#f".out","w",stdout);

struct mint {
	int x,mod;
	inline mint(int o=0,int p=998244353) {x=o;mod=p;}
	inline mint & operator=(ll o){return x=o%mod,(x+=mod)>=mod&&(x-=mod),*this;}
	inline mint & operator+=(mint o){return (x+=o.x)>=mod&&(x-=mod),*this;}
	inline mint & operator-=(mint o){return (x-=o.x)<0&&(x+=mod),*this;}
	inline mint & operator*=(mint o){return x=(ll)x*o.x%mod,*this;}
	inline mint & operator^=(ll b){mint res(1);for(;b;b>>=1,*this*=*this)if(b&1)res*=*this;return x=res.x,*this;}
	inline mint & operator^=(mint o){return *this^=o.x;}
	inline mint & operator/=(mint o){return *this*=(o^=(mod-2));}
	inline mint & operator++(){return *this+=1;}
	inline mint & operator--(){return *this-=1;}
	inline mint operator++(int o){mint res=*this;return *this+=1,res;}
	inline mint operator--(int o){mint res=*this;return *this-=1,res;}
	friend inline mint operator+(mint a,mint b){return a+=b;}
	friend inline mint operator-(mint a,mint b){return a-=b;}
	friend inline mint operator*(mint a,mint b){return a*=b;}
	friend inline mint operator/(mint a,mint b){return a/=b;}
	friend inline mint operator^(mint a,ll b){return a^=b;}
	friend inline mint operator^(mint a,mint b){return a^=b;}
	friend inline bool operator<(mint a,mint b){return a.x<b.x;}
	friend inline bool operator>(mint a,mint b){return a.x>b.x;}
	friend inline bool operator<=(mint a,mint b){return a.x<=b.x;}
	friend inline bool operator>=(mint a,mint b){return a.x>=b.x;}
	friend inline bool operator==(mint a,mint b){return a.x==b.x;}
	friend inline istream & operator>>(istream &in,mint &o){ll x;return in>>x,o=x,in;}
	friend inline ostream & operator<<(ostream &ot,mint o){return ot<<o.x,ot;}
};

#define flt double

template<typename Type> inline void read(Type &x){x=0;Type f=1,s=1;char ch=getchar();while(!isdigit(ch)&&~ch){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch))x=x*10+ch-48,ch=getchar();if(ch=='.'){ch=getchar();while(isdigit(ch))s/=10,x=x+(ch-48)*s,ch=getchar();}x*=f;}
template<typename Type> inline void write(Type x){if(x<0)putchar('-'),x=-x;if(x>9)write(x/10);putchar(x%10+'0');}
inline void read(string &s){s.clear();char ch=getchar();while(isspace(ch))ch=getchar();while(!isspace(ch)&&~ch)s+=ch,ch=getchar();}
inline void read(char &ch){ch=getchar();while(isspace(ch))ch=getchar();}
inline void read(mint &m){ll x;read(x);m=x;}
inline void write(const string &s){for(auto c:s)putchar(c);}
inline void write(const char &ch){putchar(ch);}
inline void write(const flt &x){printf("%.18LF",(long double)x);}
inline void write(const mint &m){write(m.x);}
template<typename Type,typename...T> inline void read(Type &x,T&...y){read(x),read(y...);}
template<typename Type> inline void put(const Type &x,char ch='\n'){write(x),putchar(ch);}

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

const int N=105,M=505,K=10;

int n,m,k;
vec<pii> G[N];
int p[K],f[N][1<<K];
queue<int> q;
bool inq[N];
void spfa(int s){
	while(!q.empty()){
		int x=q.front();q.pop();inq[x]=0;
		for(auto e:G[x]){
			int y=e.fir,v=e.sec;
			if(f[y][s]>f[x][s]+v){
				f[y][s]=f[x][s]+v;
				if(!inq[y])q.push(y),inq[y]=1;
			}
		}
	}
}
void solve(){
	repl(i,0,n)repl(j,0,1<<k)f[i][j]=inf;
	repl(i,0,k)f[p[i]][1<<i]=0;
	repl(s,1,1<<k){
		repl(i,0,n){
			for(int t=s&s-1;t;t=s&t-1)chmin(f[i][s],f[i][t]+f[i][s^t]);
			if(f[i][s]<inf)q.push(i),inq[i]=1;
		}
		spfa(s);
	}
	put(f[p[0]][(1<<k)-1]);
}

inline void Main(){
	read(n,m,k);
	rep(i,1,m){
		int u,v,w;
		read(u,v,w);--u,--v;
		G[u].pub({v,w});
		G[v].pub({u,w});
	}
	repl(i,0,k)read(p[i]),--p[i];
	solve();
}
int main(){
	// ReadFile(___);
	// #define MultiTasks
	#ifdef MultiTasks
	int t;read(t);while(t--)
	#endif
	Main();
	return 0;
}

[WC2008] 游览计划

简单建图,点权问题。

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template<typename Type> using vec=vector<Type>;
template<typename Type> using grheap=priority_queue<Type>;
template<typename Type> using lrheap=priority_queue<Type,vector<Type>,greater<Type>>;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define repl(i,x,y) for(int i=(x);i<(y);++i)
#define ReadFile(f) freopen(#f".in","r",stdin);freopen(#f".out","w",stdout);

struct mint {
	int x,mod;
	inline mint(int o=0,int p=998244353) {x=o;mod=p;}
	inline mint & operator=(ll o){return x=o%mod,(x+=mod)>=mod&&(x-=mod),*this;}
	inline mint & operator+=(mint o){return (x+=o.x)>=mod&&(x-=mod),*this;}
	inline mint & operator-=(mint o){return (x-=o.x)<0&&(x+=mod),*this;}
	inline mint & operator*=(mint o){return x=(ll)x*o.x%mod,*this;}
	inline mint & operator^=(ll b){mint res(1);for(;b;b>>=1,*this*=*this)if(b&1)res*=*this;return x=res.x,*this;}
	inline mint & operator^=(mint o){return *this^=o.x;}
	inline mint & operator/=(mint o){return *this*=(o^=(mod-2));}
	inline mint & operator++(){return *this+=1;}
	inline mint & operator--(){return *this-=1;}
	inline mint operator++(int o){mint res=*this;return *this+=1,res;}
	inline mint operator--(int o){mint res=*this;return *this-=1,res;}
	friend inline mint operator+(mint a,mint b){return a+=b;}
	friend inline mint operator-(mint a,mint b){return a-=b;}
	friend inline mint operator*(mint a,mint b){return a*=b;}
	friend inline mint operator/(mint a,mint b){return a/=b;}
	friend inline mint operator^(mint a,ll b){return a^=b;}
	friend inline mint operator^(mint a,mint b){return a^=b;}
	friend inline bool operator<(mint a,mint b){return a.x<b.x;}
	friend inline bool operator>(mint a,mint b){return a.x>b.x;}
	friend inline bool operator<=(mint a,mint b){return a.x<=b.x;}
	friend inline bool operator>=(mint a,mint b){return a.x>=b.x;}
	friend inline bool operator==(mint a,mint b){return a.x==b.x;}
	friend inline istream & operator>>(istream &in,mint &o){ll x;return in>>x,o=x,in;}
	friend inline ostream & operator<<(ostream &ot,mint o){return ot<<o.x,ot;}
};

#define flt double

template<typename Type> inline void read(Type &x){x=0;Type f=1,s=1;char ch=getchar();while(!isdigit(ch)&&~ch){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch))x=x*10+ch-48,ch=getchar();if(ch=='.'){ch=getchar();while(isdigit(ch))s/=10,x=x+(ch-48)*s,ch=getchar();}x*=f;}
template<typename Type> inline void write(Type x){if(x<0)putchar('-'),x=-x;if(x>9)write(x/10);putchar(x%10+'0');}
inline void read(string &s){s.clear();char ch=getchar();while(isspace(ch))ch=getchar();while(!isspace(ch)&&~ch)s+=ch,ch=getchar();}
inline void read(char &ch){ch=getchar();while(isspace(ch))ch=getchar();}
inline void read(mint &m){ll x;read(x);m=x;}
inline void write(const string &s){for(auto c:s)putchar(c);}
inline void write(const char &ch){putchar(ch);}
inline void write(const flt &x){printf("%.18LF",(long double)x);}
inline void write(const mint &m){write(m.x);}
template<typename Type,typename...T> inline void read(Type &x,T&...y){read(x),read(y...);}
template<typename Type> inline void put(const Type &x,char ch='\n'){write(x),putchar(ch);}

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

const int N=100,K=10;

int n,m,k,nn;
int a[N];
vec<pii> G[N];
int lst[N][1<<K];
int p[K],f[N][1<<K];
queue<int> q;
bool inq[N];
void spfa(int s){
	while(!q.empty()){
		int x=q.front();q.pop();inq[x]=0;
		for(auto e:G[x]){
			int y=e.fir,v=e.sec;
			if(f[y][s]>f[x][s]+v){
				f[y][s]=f[x][s]+v;
				lst[y][s]=x;
				if(!inq[y])q.push(y),inq[y]=1;
			}
		}
	}
}
bool vis[N];
void dfs(int x,int s){
	vis[x]=1;
	if(~lst[x][s])dfs(lst[x][s],s);
	for(int t=s&s-1;t;t=s&t-1)if(f[x][s]==f[x][t]+f[x][s^t]-a[x])return dfs(x,t),dfs(x,s^t);
}
char ans[N];
#define id(i,j) ((i)*m+(j))
void solve(){
	repl(i,0,n)repl(j,0,1<<k)f[i][j]=inf,lst[i][j]=-1;
	repl(i,0,k)f[p[i]][1<<i]=a[p[i]];
	repl(s,1,1<<k){
		repl(i,0,n){
			for(int t=s&s-1;t;t=s&t-1)chmin(f[i][s],f[i][t]+f[i][s^t]-a[i]);
			if(f[i][s]<inf)q.push(i),inq[i]=1;
		}
		spfa(s);
	}
	if(!k){
		put(0);
		repl(i,0,nn){repl(j,0,m)write('_');write('\n');}
		return;
	}
	put(f[p[0]][(1<<k)-1]);
	dfs(p[0],(1<<k)-1);
	repl(i,0,n)ans[i]=vis[i]?'o':'_';
	repl(i,0,k)ans[p[i]]='x';
	repl(i,0,nn){repl(j,0,m)write(ans[id(i,j)]);write('\n');}
}

inline void Main(){
	read(nn,m);
	repl(i,0,nn)repl(j,0,m)read(a[id(i,j)]);
	repl(i,0,nn)repl(j,0,m){
		if(!a[id(i,j)])p[k++]=id(i,j);
		if(i>0)G[id(i-1,j)].pub({id(i,j),a[id(i,j)]}),G[id(i,j)].pub({id(i-1,j),a[id(i-1,j)]});
		if(i<nn-1)G[id(i+1,j)].pub({id(i,j),a[id(i,j)]}),G[id(i,j)].pub({id(i+1,j),a[id(i+1,j)]});
		if(j>0)G[id(i,j-1)].pub({id(i,j),a[id(i,j)]}),G[id(i,j)].pub({id(i,j-1),a[id(i,j-1)]});
		if(j<m-1)G[id(i,j+1)].pub({id(i,j),a[id(i,j)]}),G[id(i,j)].pub({id(i,j+1),a[id(i,j+1)]});
	}
	n=nn*m;
	solve();
}
int main(){
	// ReadFile(___);
	// #define MultiTasks
	#ifdef MultiTasks
	int t;read(t);while(t--)
	#endif
	Main();
	return 0;
}

[JLOI2015] 管道连接

最小斯坦纳森林问题。

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template<typename Type> using vec=vector<Type>;
template<typename Type> using grheap=priority_queue<Type>;
template<typename Type> using lrheap=priority_queue<Type,vector<Type>,greater<Type>>;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define repl(i,x,y) for(int i=(x);i<(y);++i)
#define ReadFile(f) freopen(#f".in","r",stdin);freopen(#f".out","w",stdout);

struct mint {
	int x,mod;
	inline mint(int o=0,int p=998244353) {x=o;mod=p;}
	inline mint & operator=(ll o){return x=o%mod,(x+=mod)>=mod&&(x-=mod),*this;}
	inline mint & operator+=(mint o){return (x+=o.x)>=mod&&(x-=mod),*this;}
	inline mint & operator-=(mint o){return (x-=o.x)<0&&(x+=mod),*this;}
	inline mint & operator*=(mint o){return x=(ll)x*o.x%mod,*this;}
	inline mint & operator^=(ll b){mint res(1);for(;b;b>>=1,*this*=*this)if(b&1)res*=*this;return x=res.x,*this;}
	inline mint & operator^=(mint o){return *this^=o.x;}
	inline mint & operator/=(mint o){return *this*=(o^=(mod-2));}
	inline mint & operator++(){return *this+=1;}
	inline mint & operator--(){return *this-=1;}
	inline mint operator++(int o){mint res=*this;return *this+=1,res;}
	inline mint operator--(int o){mint res=*this;return *this-=1,res;}
	friend inline mint operator+(mint a,mint b){return a+=b;}
	friend inline mint operator-(mint a,mint b){return a-=b;}
	friend inline mint operator*(mint a,mint b){return a*=b;}
	friend inline mint operator/(mint a,mint b){return a/=b;}
	friend inline mint operator^(mint a,ll b){return a^=b;}
	friend inline mint operator^(mint a,mint b){return a^=b;}
	friend inline bool operator<(mint a,mint b){return a.x<b.x;}
	friend inline bool operator>(mint a,mint b){return a.x>b.x;}
	friend inline bool operator<=(mint a,mint b){return a.x<=b.x;}
	friend inline bool operator>=(mint a,mint b){return a.x>=b.x;}
	friend inline bool operator==(mint a,mint b){return a.x==b.x;}
	friend inline istream & operator>>(istream &in,mint &o){ll x;return in>>x,o=x,in;}
	friend inline ostream & operator<<(ostream &ot,mint o){return ot<<o.x,ot;}
};

#define flt double

template<typename Type> inline void read(Type &x){x=0;Type f=1,s=1;char ch=getchar();while(!isdigit(ch)&&~ch){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch))x=x*10+ch-48,ch=getchar();if(ch=='.'){ch=getchar();while(isdigit(ch))s/=10,x=x+(ch-48)*s,ch=getchar();}x*=f;}
template<typename Type> inline void write(Type x){if(x<0)putchar('-'),x=-x;if(x>9)write(x/10);putchar(x%10+'0');}
inline void read(string &s){s.clear();char ch=getchar();while(isspace(ch))ch=getchar();while(!isspace(ch)&&~ch)s+=ch,ch=getchar();}
inline void read(char &ch){ch=getchar();while(isspace(ch))ch=getchar();}
inline void read(mint &m){ll x;read(x);m=x;}
inline void write(const string &s){for(auto c:s)putchar(c);}
inline void write(const char &ch){putchar(ch);}
inline void write(const flt &x){printf("%.18LF",(long double)x);}
inline void write(const mint &m){write(m.x);}
template<typename Type,typename...T> inline void read(Type &x,T&...y){read(x),read(y...);}
template<typename Type> inline void put(const Type &x,char ch='\n'){write(x),putchar(ch);}

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

const int N=1005,M=3005,K=10;

int n,m,k;
vec<pii> G[N];
int c[K],p[K],f[N][1<<K],g[1<<K];
int col[K];
queue<int> q;
bool inq[N];
void spfa(int s){
	while(!q.empty()){
		int x=q.front();q.pop();inq[x]=0;
		for(auto e:G[x]){
			int y=e.fir,v=e.sec;
			if(f[y][s]>f[x][s]+v){
				f[y][s]=f[x][s]+v;
				if(!inq[y])q.push(y),inq[y]=1;
			}
		}
	}
}
void solve(){
	repl(i,0,n)repl(j,0,1<<k)f[i][j]=inf;
	repl(i,0,k)f[p[i]][1<<i]=0;
	repl(s,1,1<<k){
		repl(i,0,n){
			for(int t=s&s-1;t;t=s&t-1)chmin(f[i][s],f[i][t]+f[i][s^t]);
			if(f[i][s]<inf)q.push(i),inq[i]=1;
		}
		spfa(s);
	}
	repl(s,1,1<<k){
		g[s]=inf;
		if([&](){repl(i,0,10)if(col[i]^col[i]&s&&col[i]&s)return 0;return 1;}())
			repl(j,0,n)chmin(g[s],f[j][s]);
		for(int t=s&s-1;t;t=s&t-1)chmin(g[s],g[t]+g[s^t]);
	}
	put(g[(1<<k)-1]);
}

inline void Main(){
	read(n,m,k);
	rep(i,1,m){
		int u,v,w;
		read(u,v,w);--u,--v;
		G[u].pub({v,w});
		G[v].pub({u,w});
	}
	repl(i,0,k)read(c[i],p[i]),--c[i],--p[i],col[c[i]]|=1<<i;
	solve();
}
int main(){
	// ReadFile(___);
	// #define MultiTasks
	#ifdef MultiTasks
	int t;read(t);while(t--)
	#endif
	Main();
	return 0;
}

[THUSC 2017] 巧克力

题解

posted @ 2025-06-23 21:35  LastKismet  阅读(115)  评论(0)    收藏  举报