斯坦纳树
学习资料:OI Wiki
问题:
在一个无向带权连通图 (G=(V,E)) 中,对点集 (Ssubset V) 求一边集 (Csubset E) ,使得 (uin S) 相互连通且 (sum w_c(cin C)) 的值最小。
也就是,对一个无向图 (G),求一个对于点集 (S) 的最小生成树。
做法:
状压+dp+最短路
时间复杂度:
(mathcal{O(n imes3^k+mlogm imes2^k)}) , (|S|=k)
模板题:
代码:
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
const int inf=0x3f3f3f3f;
int head[maxn],to[maxn<<1],nxt[maxn<<1],ww[maxn<<1],pcnt;
inline void add(int u,int v,int w){
to[++pcnt]=v;nxt[pcnt]=head[u];ww[pcnt]=w;head[u]=pcnt;
}
int a[maxn];
int dp[111][2222];
priority_queue<pair<int,int>>que;
void Dij(int sta)
{
pair<int,int>pr;
while(!que.empty())
{
pr=que.top();que.pop();
for(int i=head[pr.second];i;i=nxt[i]){
if(dp[to[i]][sta]>dp[pr.second][sta]+ww[i]){
dp[to[i]][sta]=dp[pr.second][sta]+ww[i];
que.push(mkp(-dp[to[i]][sta],to[i]));
}
}
}
}
int main()
{
int n,m,k,u,v,w;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
for(int i=1;i<=k;i++)scanf("%d",&a[i]);
mem(dp,inf);
for(int i=1;i<=k;i++)dp[a[i]][1<<i-1]=0;
for(int sta=1,up=1<<k;sta<up;sta++){
for(int i=1;i<=n;i++)
{
for(int s=sta&(sta-1);s;s=sta&(s-1))
dp[i][sta]=min(dp[i][sta],dp[i][sta^s]+dp[i][s]);
if(dp[i][sta]!=inf)que.push(mkp(-dp[i][sta],i));
}
Dij(sta);
}
printf("%d
",dp[a[1]][(1<<k)-1]);
}