斯坦纳树(学习笔记)
学习博客:https://www.cnblogs.com/ECJTUACM-873284962/p/7643445.html
斯坦纳树:
给定k个点,要求将k个点联通,且权值和最小
f[i][s]表示以i为根,当前联通集合为s的最小代价
转移方程有两层:
第一层,通过连通状态的子集进行转移
f[i][s]=min{ f[i][sub]+f[i][s-sub]-重复权值}
枚举子集: for(int sub=(s-1)&s;sub;sub=(sub-1)&s)
第二重,在当前枚举的连通状态下,对该连通状态进行松弛操作
f[i][s]=min{ f[i][s], f[j][s]+e[i][j] }
为什么只需对该连通状态进行松弛?因为更后面的连通状态会由先前的连通状态通过第一重转移得到,所以无需对别的连通状态松弛。松弛操作用SPFA即可。
例题:bzoj2595 WC2008游览计划
f[i][j][k]表示以a[i][j]为根,联通状态为k的最小代价
输出方案要记录一下每个状态是从哪个状态转移过来的,然后倒着递归回去找就好了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> #include<stack> #include<cmath> using namespace std; typedef long long ll; typedef pair<int,int> P; const int maxn = 15; int n,m,cnt,INF; int f[maxn][maxn][1<<maxn],a[maxn][maxn]; int vis[maxn][maxn]; struct Sta{ int x,y,z; }pre[maxn][maxn][1<<maxn]; int dx[maxn]={1,-1,0,0}; int dy[maxn]={0,0,1,-1}; queue<P> q; void spfa(int sta){ while(!q.empty()){ P u=q.front(); q.pop(); int x=u.first,y=u.second; vis[x][y]=0; for(int i=0;i<4;i++){ int xx=x+dx[i],yy=y+dy[i]; if(xx<0||xx>=n||yy<0||yy>=m) continue; if(f[xx][yy][sta]>f[x][y][sta]+a[xx][yy]){ f[xx][yy][sta]=f[x][y][sta]+a[xx][yy]; pre[xx][yy][sta]=(Sta){x,y,sta}; if(!vis[xx][yy]){ q.push(P(xx,yy)); vis[xx][yy]=1; } } } } } void dfs(int i,int j,int k){ // 倒序找方案 if(pre[i][j][k].z==0) return; Sta u=pre[i][j][k]; int x=u.x,y=u.y,z=u.z; vis[x][y]=1; dfs(x,y,z); if(i==x&&j==y) dfs(i,j,k-z); } ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f;} int main(){ n=read(),m=read(); memset(f,0x3f,sizeof(f)); INF=f[0][0][0]; for(int i=0;i<n;++i){ for(int j=0;j<m;++j){ a[i][j]=read(); if(!a[i][j]){ f[i][j][1<<cnt]=0; cnt++; } } } for(int k=0;k<(1<<cnt);++k){ // 是由子集状态转移过来的,所以要放在外层 for(int i=0;i<n;++i){ for(int j=0;j<m;++j){ for(int s=(k-1)&k;s;s=(s-1)&k){ if(f[i][j][k]>f[i][j][s]+f[i][j][k-s]-a[i][j]){ f[i][j][k]=f[i][j][s]+f[i][j][k-s]-a[i][j]; pre[i][j][k]=(Sta){i,j,s}; } } if(f[i][j][k]!=INF){ q.push(P(i,j)); vis[i][j]=1; } } } spfa(k); // 每更新完一层就松弛一层 } int x,y; for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(!a[i][j]) x=i,y=j; // 为什么? 不取最小值? printf("%d\n",f[x][y][(1<<cnt)-1]); dfs(x,y,(1<<cnt)-1); for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ if(!a[i][j]) printf("x"); else if(vis[i][j]) printf("o"); else printf("_"); }printf("\n"); } return 0; }