BZOJ 2595: [Wc2008]游览计划 斯坦纳树

题意:连通n个景点的最小生成树(斯坦纳树)
用spfa转移dp方程,具体转移方式见hdu4085博文
此题还需要输出哪些边被用到了。这里记录一下每次转移的前继,最后从最优解开始,沿着前继边dfs,记录一下被访问到的边,最后按顺序输出被标记的边即可。
注:可以用三个数组记录前继,写起来更方便

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<map>
#define PIII pair< pair<int,int>, int>
#define X first
#define Y second
#define MP make_pair
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
using namespace std;

const int maxn = 15;
const int maxm = 1200;
const int INF = 0x3f3f3f3f;
const int maxq = maxn*maxn;
int n, m, cnt, ma[maxn][maxn], dp[maxn][maxn][maxm], hd, tail;
int q[maxq], vis[maxq];
int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
PIII pre[maxn][maxn][maxm];

void spfa(int s){
    while(tail != hd){
        int u = q[hd++], x = u/m+1, y = u%m+1; hd %= maxq, vis[u] = 0;
        for(int k = 0; k < 4; k ++){
            int nx = x + dir[k][0], ny = y + dir[k][1], v = (nx-1)*m+ny-1;
            if(nx <= 0 || nx > n || ny <= 0 || ny > m) continue;
            if(dp[nx][ny][s] > dp[x][y][s] + ma[nx][ny]){
                dp[nx][ny][s] = dp[x][y][s] + ma[nx][ny];
                pre[nx][ny][s] = MP(MP(x, y), s);
                if(!vis[v]) q[tail++] = v, tail %= maxq, vis[v] = 1;
            }
        }
    }
}

void dfs(int i, int j, int k){
    vis[(i-1)*m+j-1] = 1;
    if(pre[i][j][k].X.X == -1) return;
    dfs(pre[i][j][k].X.X,pre[i][j][k].X.Y,pre[i][j][k].Y);
    if(pre[i][j][k].X.X == i && pre[i][j][k].X.Y == j) dfs(pre[i][j][k].X.X,pre[i][j][k].X.Y, k ^ pre[i][j][k].Y);
}

int main(){
    //freopen("input.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    mem(dp, INF);
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++){
            scanf("%d", &ma[i][j]);
            if(!ma[i][j]) dp[i][j][1<<cnt] = 0, cnt++;
        }
    for(int k = 1; k < (1<<cnt); k ++){
        hd = tail = 0;
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= m; j ++){
                pre[i][j][k] = MP(MP(-1, -1), -1);
                for(int s = k; s; s = (s-1)&k){
                    if(dp[i][j][k] > dp[i][j][s] + dp[i][j][k-s] - ma[i][j]){
                        dp[i][j][k] = dp[i][j][s] + dp[i][j][k-s] - ma[i][j], pre[i][j][k] = MP(MP(i, j), s);
                    }
                }
                if(dp[i][j][k] < INF) vis[(i-1)*m+j-1] = 1, q[tail++] = (i-1)*m+j-1, tail %= maxq;
            }
        spfa(k);
    }
    int ans = INF, x, y;
    for(int i = 1; i <= n; i ++) for(int j = 1; j<= m; j ++)
        if(dp[i][j][(1<<cnt)-1] < ans) ans = dp[i][j][(1<<cnt)-1], x = i, y = j;
    printf("%d\n", ans);
    mem(vis, 0);
    dfs(x, y, (1<<cnt)-1);
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m; j ++){
            if(!ma[i][j]) printf("x");
            else if(vis[(i-1)*m+j-1]) printf("o");
            else printf("_");
        }
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值