[学习笔记]斯坦纳树

处理一种这样的问题:

斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。

——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中房屋的数量大于庇护所的数量,不能转移

 

posted @ 2019-01-06 21:10  *Miracle*  阅读(3552)  评论(0编辑  收藏  举报