LINK

和普通斯坦纳树不太一样,这里是求一个斯坦纳树森林

只要求同色点在一个连通块内

考虑先做一遍普通斯坦纳树

想形成斯坦纳树森林,比如是由几个完整的斯坦纳树合并而来

完整指的是,状态中若包含颜色 x x x,就必须包含所有颜色 x x x的点

基于上面这个信息,我们可以得到一个 g g g数组, g [ s ] g[s] g[s]表示状态为 s s s的斯坦纳树的最小花费

目前的 g [ s ] g[s] g[s]都是围绕某个中心点形成的连通块,所以我们可以合并树为森林

这么这个合并过程可以枚举子集来完成

当然,枚举的子集也需要是完整的斯坦纳树

不过就算你无脑枚举子集也不会错,因为如果不完整的斯坦纳树合并起来一定不优

无法影响到正确答案

#include <bits/stdc++.h>
using namespace std;
const int N = 2009;
const int maxn = 2e5+10;
int n,m,pp;
struct edge{
	int to,nxt,w;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int w)
{
	d[++cnt] = ( edge ){v,head[u],w},head[u] = cnt;
}
int f[N][1<<12],vis[N],sta[1<<12],g[1<<12],col[12],dian[12];
void spfa(int s)
{
	queue<int>q;
	for(int i=1;i<=n;i++)	vis[i] = 0;
	for(int i=1;i<=n;i++)	if( f[i][s]!=0x3f3f3f3f )	q.push(i);
	while( !q.empty() )
	{
		int u = q.front(); q.pop();
		vis[u] = 0;
		for(int i=head[u];i;i=d[i].nxt )
		{
			int v = d[i].to;
			if( f[v][s]>f[u][s]+d[i].w )
			{
				f[v][s] = f[u][s]+d[i].w;
				if( !vis[v] )	q.push(v);
			}
		}
	}
}
bool ok[1<<12];
bool isok(int s)
{
	for(int i=1;i<=pp;i++)//枚举所有颜色 
		if( (s&sta[i])!=0 && (s&sta[i])!=sta[i] )	return false;
	return true;
}
void sitanna(int mx)
{
	memset( f,0x3f,sizeof f );
	memset( g,0x3d,sizeof g );
	for(int i=1;i<=pp;i++)
	{
		cin >> col[i] >> dian[i];
		sta[col[i]] |= (1<<(i-1));
		f[dian[i]][1<<(i-1)] = 0;
	}
	for(int i=0;i<mx;i++)	ok[i] = isok(i);
	
	for(int s=1;s<mx;s++)
	{
		for(int i=1;i<=n;i++)
		for(int j=s&(s-1);j;j=s&(j-1) )
			f[i][s] = min( f[i][s],f[i][j]+f[i][s^j] );
		spfa(s);
		if( ok[s]==false )	continue;
		for(int i=1;i<=n;i++)	g[s] = min( g[s],f[i][s] );//取最优值 
	}
	
	for(int s=1;s<mx;s++)
	{
		if( ok[s]==false )	continue; 
		for(int j=s&(s-1);j;j=s&(j-1) )
			if( ok[j] )	g[s] = min( g[s],g[j]+g[s^j] ); 
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&pp);
	for(int i=1;i<=m;i++)
	{
		int l,r,w; scanf("%d%d%d",&l,&r,&w);
		add(l,r,w); add(r,l,w);
	}
	int mx = (1<<pp);
	sitanna( mx );
	cout << g[mx-1];
}