【状压DP】斯坦纳树

前言

斯坦纳树常用于解决类似这样的最小代价联通问题:https://www.luogu.com.cn/problem/P4294

思路

tip1:
对于一个结点, 通过状态压缩的思想,用一个整数sta表示这个结点与所有目标节点的联通状态, 在sta的二进制位中,若第x位为1,表示该结点与第x个目标结点联通,反之则为不连通
用dp[i][sta]表示结点i联通sta状态的最小代价

状态转移part1
若结点i与状态sta1和sta2都联通,且sta1与sta2无重合结点(即sta1, sta2 是对sta1+sta2的一个划分)。令P表示把点i联通所需的代价, 显然有:
dp[i][sta1 + sta2] = MIN(dp[i][sta1 + sta2], dp[i][sta1] + dp[i][sta2] - P[i])
由于dp[i][sta1] 和dp[i][sta2]都包含了联通点i的代价P[i],所以要减去一个P[i]。
dp[i][sta1]中的[i]只是一种结点的表示方法.
状态转移part2
按照边进行松弛
对于一个结点i, dp[i][sta] = MIN(dp[i][sta], dp[i周围一步可达的结点][sta] + P[i])
像极了最短路, 直接spfa

题目

P4294 [WC2008]游览计划

https://www.luogu.com.cn/problem/P4294

由于你还需要输出所有位置的摆放 所以加入了pre数组记录某状态的前置状态。得出最小答案后通过dfs找出斯坦纳树的所有结点。
(对于状态转移part1, 只需要记录一个前置状态,另一个前置状态通过减法求出)

#include <bits/stdc++.h>
const int N = 13, LIM = 1028, INF = 0x3f3f3f3f;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
int board[N][N], dp[N][N][LIM];
int n, m, idx, mSta, rex, rey;
bool vis[N][N], judge;
struct PRE{
    int x, y, s;
}pre[N][N][LIM];
void dfs(int x, int y, int st){
    vis[x][y] = true;
    PRE tmp = pre[x][y][st];
    if(!tmp.x && !tmp.y) return ;
    dfs(tmp.x, tmp.y, tmp.s);
    if(tmp.x == x && tmp.y == y) dfs(x, y, st - tmp.s);
}
inline bool cango(int x, int y) {return x<=n && y<=m && x>=1 && y>=1; }
int main(){
    memset(dp, 63, sizeof(dp));
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) {
        scanf("%d", &board[i][j]);
        if(!board[i][j]) dp[i][j][1 << idx ++] = 0;
    }
    mSta = 1 << idx;
    for(int sta=1; sta<mSta; sta++){
        std::queue<std::pair<int ,int> > q;
        for(int i=1; i<=n; i++) for(int j=1; j<=m; j++){
            for(int sub=sta; sub; sub=(sub-1)&sta){
                if(dp[i][j][sub] + dp[i][j][sta - sub] - board[i][j] < dp[i][j][sta]){
                    dp[i][j][sta] = dp[i][j][sub] + dp[i][j][sta - sub] - board[i][j];
                    pre[i][j][sta] = (PRE){i, j, sub};
                }
            }
            if(dp[i][j][sta] < INF) q.push(std::make_pair(i, j)), vis[i][j] = true;
        }
        while(!q.empty()){
            std::pair<int , int> x = q.front(); q.pop(); vis[x.first][x.second] = false;
            for(int i=0; i<4; i++){
                int xx = x.first + dx[i], yy = x.second + dy[i];
                if(cango(xx, yy)) if(dp[x.first][x.second][sta] + board[xx][yy] < dp[xx][yy][sta]){
                    dp[xx][yy][sta] = dp[x.first][x.second][sta] + board[xx][yy];
                    pre[xx][yy][sta] = (PRE){x.first, x.second, sta};
                    if(!vis[xx][yy]) vis[xx][yy] = true, q.push(std::make_pair(xx, yy));
                }
            }
        }
    }
    for(int i=1; i<=n && !judge; i++) for(int j=1; j<=m && !judge; j++) if(!board[i][j]) rex = i, rey = j, judge = true;
    printf("%d\n", dp[rex][rey][mSta - 1]);
    dfs(rex, rey, mSta - 1);
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++){
            if(board[i][j] == 0) putchar('x');
            else if(vis[i][j] == 1) putchar('o');
            else putchar('_');
        }
        putchar('\n');
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值