处理一种这样的问题:
斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。
——by 度娘
简单来讲
一个无向图,k个关键点,每个边有边权,求联通这k个点的最小代价
最小生成树可以认为是斯坦纳树的特殊情况
由于k个关键点的高度要求精确打击,只能用状压
所以k一般最多到10
n也不会太大
方法:
f[i][S]以i为根的树,连接了S集合关键点,的最少代价
i为根,i只是一个表示,为了转移时候连边方便
外层循环S
第一个dp:f[i][S]=min(f[i][t]+f[i][S^t])直接通过自己原来的构造转移
第二个dp:f[i][S]=min(f[i][S],f[k][S]+e[i][k])通过别的点连一条边过来
第二个dp明显有环,所以spfa处理一下
第一个dp更新完了之后,所有不是inf的点都加入queue,然后spfa
可以证明最优解一定可以找到
例题:
模板题
输出决策的时候,用pre记录前驱,注意区分两个dp转移过来方式的区别。pre不会有环,所以最后dfs一下 即可
#include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=11; const int P=101; const int inf=0x3f3f3f3f; int mv[4][2]={{+1,0},{-1,0},{0,+1},{0,-1}}; int f[N][N][1<<10]; pair<int,int>pre[N][N][1<<10]; int id[N][N]; struct po{ int x,y; po(){} po(int xx,int yy){ x=xx;y=yy; } }; queue<po>q; int n,m,k; int mp[N][N]; char op[N][N]; bool vis[N][N]; void spfa(int s){ while(!q.empty()){ po now=q.front();q.pop(); vis[now.x][now.y]=0; for(reg i=0;i<4;++i){ int dx=now.x+mv[i][0],dy=now.y+mv[i][1]; if(dx<1||dx>n) continue; if(dy<1||dy>m) continue; if(f[dx][dy][s]>f[now.x][now.y][s]+mp[dx][dy]){ f[dx][dy][s]=f[now.x][now.y][s]+mp[dx][dy]; pre[dx][dy][s]=make_pair(now.x,now.y); if(!vis[dx][dy]){ vis[dx][dy]=1; q.push(po(dx,dy)); } } } } } void check(int i,int j,int s){ if(id[i][j]==0) op[i][j]='o'; if(pre[i][j][s].first!=-1){ if(pre[i][j][s].first==0){ check(i,j,pre[i][j][s].second); check(i,j,s^pre[i][j][s].second); }else{ check(pre[i][j][s].first,pre[i][j][s].second,s); } }else return; } int main(){ rd(n);rd(m); for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ rd(mp[i][j]); if(mp[i][j]==0){ ++k;id[i][j]=k; op[i][j]='x'; } } } memset(f,inf,sizeof f); for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ if(mp[i][j]==0){ f[i][j][1<<(id[i][j]-1)]=0; pre[i][j][1<<(id[i][j]-1)]=make_pair(-1,-1); } f[i][j][0]=0; pre[i][j][0]=make_pair(-1,-1); } } for(reg s=1;s<(1<<k);++s){ for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ for(reg t=(s-1)&s;t;t=(t-1)&s){ if(f[i][j][s]>f[i][j][t]+f[i][j][s^t]-mp[i][j]){ f[i][j][s]=f[i][j][t]+f[i][j][s^t]-mp[i][j]; pre[i][j][s]=make_pair(0,t); } } if(f[i][j][s]!=inf) q.push(po(i,j)),vis[i][j]=1; } } spfa(s); } int tx,ty; int ans=inf; for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ if(f[i][j][(1<<k)-1]<ans){ ans=f[i][j][(1<<k)-1]; tx=i;ty=j; } } } check(tx,ty,(1<<k)-1); printf("%d ",ans); for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ if(!op[i][j]) op[i][j]='_'; putchar(op[i][j]); }puts(""); } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/1/6 19:03:36 */
最后答案可能是一个森林
先不管,先跑斯坦纳树一遍。S设为2k大小。因为答案最多加入2k个
然后g[S]表示考虑S集合,最小代价。
枚举子集合并斯坦纳树
注意如果S中房屋的数量大于庇护所的数量,不能转移