斯坦纳树(学习笔记)

学习博客: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;
}
View Code

 

posted @ 2019-02-25 08:55  Tartarus_li  阅读(479)  评论(0编辑  收藏  举报