【BZOJ4774】修路(动态规划,斯坦纳树)
题面
题解
先讲怎么求解最小斯坦纳树。
先明白什么是斯坦纳树。
斯坦纳树可以认为是最小生成树的一般情况。最小生成树是把所有给定点都要加入到联通块中。而斯坦纳树不一样,斯坦纳树只需要把指定点集中的所有点全部加入到联通块中,并且允许使用点集以外的点。
然而求解最小斯坦纳树是一个\(NP\)问题,所以只能状压解决。
设\(f[S][i]\)表示指定点的联通情况为\(S\),并且当且的斯坦纳树以\(i\)为根,\(i\)可以是图上任意一个点。
考虑如何转移:
\(f[S][i]\rightarrow f[T][i]+f[S\oplus T][i],S\&T=0\)
这个转移的含义是,你以当前点为根的斯坦纳树,可以拆分为两个以当前点为根的斯坦纳树。
另外一个转移是换根:
\(f[S][i]\rightarrow f[S][j]+e[i][j]\),其中\(e[i][j]\)是链接\(i,j\)的边的边长。
这一步你可以认为一开始没有联通\(j\),现在我们换根,所以要把它给添加进来。
但是发现第二个转移具有后效性,所以写成\(SPFA\)的形式。
接着考虑怎么求解本题的问题,也就是最小斯坦纳森林。
设\(g[S]\)表示联通了点集\(S\)的最小斯坦纳森林。
那么,如果\(S\)中要求在一个联通块的点全部都连在了一起,那么显然它可以和一个无交集,并且同时满足要求连接在一起的点都连在一起的话,这两个集合显然可以取并集转移。
即\(g[S]=g[T]+g[S\oplus T]\),条件是\(T\)和\(S\oplus T\)这两个集合中如果包含了某个点,就必定包含要求连接在一起的点。
这样子就做完了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 10100
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
int n,m,D;
struct Line{int v,next,w;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int f[1<<8][MAX],g[1<<8];
bool vis[MAX];
queue<int> Q;
void SPFA(int *f)
{
while(!Q.empty())
{
int u=Q.front();Q.pop();
for(int i=h[u];i;i=e[i].next)
if(f[e[i].v]>f[u]+e[i].w)
{
f[e[i].v]=f[u]+e[i].w;
if(!vis[e[i].v])Q.push(e[i].v),vis[e[i].v]=true;
}
vis[u]=false;
}
}
bool check(int s){return (s&((1<<D)-1))==(s>>D);}
int main()
{
n=read();m=read();D=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read(),w=read();
Add(u,v,w);Add(v,u,w);
}
memset(f,63,sizeof(f));memset(g,63,sizeof(g));
for(int i=1;i<=D;++i)f[1<<(i-1)][i]=f[1<<(D+i-1)][n-i+1]=0;
int S=1<<(D<<1);
for(int i=0;i<S;++i)
{
for(int j=1;j<=n;++j)
{
for(int k=i&(i-1);k;k=(k-1)&i)
f[i][j]=min(f[i][j],f[k][j]+f[i^k][j]);
if(f[i][j]<=1e9)Q.push(j),vis[j]=true;
}
SPFA(f[i]);
for(int j=1;j<=n;++j)g[i]=min(g[i],f[i][j]);
}
for(int i=0;i<S;++i)
for(int t=(i-1)&i;t;t=(t-1)&i)
if(check(t)&&check(i^t))
g[i]=min(g[i],g[t]+g[i^t]);
printf("%d\n",g[S-1]<=1e9?g[S-1]:-1);
return 0;
}