[學習筆記]斯坦納樹


處理一種這樣的問題:

斯坦納樹問題是組合優化問題,與最小生成樹相似,是最短網絡的一種。最小生成樹是在給定的點集和邊中尋求最短網絡使所有點連通。而最小斯坦納樹允許在給定點外增加額外的點,使生成的最短網絡開銷最小。

——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

可以證明最優解一定可以找到

 

例題:

[WC2008]游覽計划

模板題

輸出決策的時候,用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
*/
View Code

 

Peach Blossom Spring

最后答案可能是一個森林

先不管,先跑斯坦納樹一遍。S設為2k大小。因為答案最多加入2k個

然后g[S]表示考慮S集合,最小代價。

枚舉子集合並斯坦納樹

注意如果S中房屋的數量大於庇護所的數量,不能轉移

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM