bzoj 2595 [Wc2008]游览计划 状压dp+斯坦纳树+spfa

题面

题目传送门

解法

斯坦纳树的经典应用

  • 问题就是给 k k 个关键点,构造一个连通块,使得这k个点连通且代价最小,斯坦纳树就可以解决这类问题
  • 其实 k k 和棋盘的大小都并没有那么大,只有10,所以我们不妨考虑状压dp
  • 说白了,斯坦纳树这个名词听起来确实挺nb的,但是冷静一下发现就是一个状压dp
  • 可以发现,只有2个关键点的时候一定是走最短路,如果出现超过2个点,就不会是最小生成树的情况,中间可能会选择在一个不是关键点的地方分叉
  • 所以,我们考虑这样设计状态: fi,j,s f i , j , s 表示当前分叉点假设为 (i,j) ( i , j ) ,关键点的连通状态为 s s 的最小代价
  • 显然,转移只有2种
  • 枚举子集,即fi,j,s=min(fi,j,k+fi,j,skai,j),减去 ai,j a i , j 是因为它在转移的时候被重复计算了一次
  • 当然,也可以是 (i,j) ( i , j ) 连向另外某一个点,然后这个点对关键点的连通状态为 s s ,即fi,j,s=min(fi,j,s+dis(i,j,i,j))
  • 但是可以发现,其实我们对于第二种转移并不需要用 O(nm) O ( n m ) 的复杂度枚举那个点在哪里,我们可以通过周围的四个点来迭代出自己的最优解
  • 所以,我们对于第二种转移,可以先找到存在解的位置,然后用spfa更新一下即可,这样就省去了每一次转移 O(nm) O ( n m ) 的复杂度,可以直接在spfa中求出来
  • 因为题目中需要求出具体的方案,所以考虑在转移的时候记录一下这个状态是由之前的哪一个状态转移到的即可,然后我们就可以发现第二种转移如果要枚举点的话对于方案的输出并不是那么好处理
  • 时间复杂度: O(nm×(3k+nm)) O ( n m × ( 3 k + n m ) )

【注意事项】

  • 建议处理好边界条件,否则可能会挂,只针对我这种蒟蒻
  • 枚举子集的时候注意一下,我一开始就是因为枚举子集没有枚举全部的子集(就是写错了)然后就WA光了

代码

#include <bits/stdc++.h>
#define N 11
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Node {
    int x, y;
} b[N];
struct Info {
    int x, y, s;
} pre[N][N][1 << N];
int dx[5] = {0, -1, 1, 0, 0}, dy[5] = {0, 0, 0, -1, 1};
int n, m, a[N][N], vis[N][N], used[N * N], f[N][N][1 << N];
queue <Node> q;
int lowbit(int x) {return x & -x;}
int calc(int x, int y) {return (x - 1) * m + y;}
void spfa(int s) {
    while (!q.empty()) {
        Node tmp = q.front(); q.pop();
        int x = tmp.x, y = tmp.y; used[calc(x, y)] = 0;
        for (int i = 1; i <= 4; i++) {
            int tx = x + dx[i], ty = y + dy[i];
            if (tx && ty && tx <= n && ty <= m && f[tx][ty][s] > f[x][y][s] + a[tx][ty]) {
                f[tx][ty][s] = f[x][y][s] + a[tx][ty];
                pre[tx][ty][s] = (Info) {x, y, s};
                if (!used[calc(tx, ty)]) used[calc(tx, ty)] = 1, q.push((Node) {tx, ty});
            }
        }
    }
}
void dfs(int x, int y, int s) {
    if (!s) return;
    Info tmp = pre[x][y][s]; vis[x][y] = 1;
    dfs(tmp.x, tmp.y, tmp.s);
    if (tmp.x == x && tmp.y == y) dfs(tmp.x, tmp.y, s - tmp.s);
}
int main() {
    read(n), read(m); int t = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int s = 0; s < (1 << 10); s++)
                f[i][j][s] = INT_MAX;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            read(a[i][j]), f[i][j][0] = a[i][j];
            if (!a[i][j]) b[++t] = (Node) {i, j};
        }
    for (int i = 1; i <= t; i++) {
        f[b[i].x][b[i].y][1 << i - 1] = 0;
        q.push(b[i]); spfa(1 << i - 1);
    }
    for (int s = 1; s < (1 << t); s++) {
        if (s == lowbit(s)) continue;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) {
                for (int k = s & (s - 1); k; k = s & (k - 1))
                    if (f[i][j][s] > f[i][j][s - k] + f[i][j][k] - a[i][j]) {
                        f[i][j][s] = f[i][j][k] + f[i][j][s - k] - a[i][j];
                        pre[i][j][s] = (Info) {i, j, k};
                    }
                if (f[i][j][s] != INT_MAX) q.push((Node) {i, j}), used[calc(i, j)] = 1;
            }
        spfa(s);
    }
    int ans = INT_MAX, x, y;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (f[i][j][(1 << t) - 1] < ans)
                ans = f[i][j][(1 << t) - 1], x = i, y = j;
    cout << ans << "\n"; dfs(x, y, (1 << t) - 1);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
            if (!a[i][j]) cout << 'x';
                else if (vis[i][j]) cout << 'o';
                    else cout << '_';
        cout << "\n";
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值