和普通斯坦纳树不太一样,这里是求一个斯坦纳树森林
只要求同色点在一个连通块内
考虑先做一遍普通斯坦纳树
想形成斯坦纳树森林,比如是由几个完整的斯坦纳树合并而来
完整指的是,状态中若包含颜色 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];
}