處理一種這樣的問題:
斯坦納樹問題是組合優化問題,與最小生成樹相似,是最短網絡的一種。最小生成樹是在給定的點集和邊中尋求最短網絡使所有點連通。而最小斯坦納樹允許在給定點外增加額外的點,使生成的最短網絡開銷最小。
——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\n",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中房屋的數量大於庇護所的數量,不能轉移
