炸弹 树形地图

题目大意:在一个50*50的地图内,有些是空地,另外的是障碍物,空地是连成一棵树。若在某空地放置一个炸弹,那么在同一列和同一行且没有障碍物阻拦的格子都被炸到(类似于泡泡堂。。。)。询问至少需要多少个炸弹才能把所有的格子都炸到。

题目中很关键的一个字眼就所有的空地是一棵树,这促使我们往树形动态规划的方面去想。

构建一棵二叉树,左子树表示在同一行的空地,右子树表示同一列的空地。那么,若把某个节点炸掉,受到影响的将会是它的父亲和祖先,左孩子和左孩子的左孩子,左孩子的左孩子的左孩子,以及右孩子,右孩子的右孩子....。对每个点记录两个状态,一个是该节点被炸以后,有一个向左上的威力把父亲炸掉,以及有一个右上的威力把父亲炸掉。

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
using namespace std;


#define st first
#define nd second
#define pi 3.141592653589793238462643383


const int dx[4] = {0, 0, 1, -1};
const int dy[4] = {1, -1, 0, 0};
const int inf = 0x3f3f3f3f;


#define Rep(i, n) for (int i = 0, _n = n; i < _n; i ++)
#define Repp(i, n) for (int i = 1, _n = n; i <= _n; i ++)
#define For(i, a, b) for (int i = a, _b = b; i <= _b; i ++)
#define Forr(i, a, b) for (int i = a, _b = b; i >= _b; i --)


typedef long long ll;
typedef pair<int, int> pii;


#define maxn 57
#define maxp maxn*maxn


int n, m, sx, sy;
int dat[maxn][maxn];


struct Pointer{
    Pointer *lc, *rc;
    int lf, rf;
    
    int get() {
        return min(lf, rf);
    }
}head[maxp], *nll = head;


void Init() {
    scanf("%d%d\n", &n, &m);
    
    nll -> lf = nll -> rf = 0;
    
    int cnt = 0;    char ch;
    Repp(i, n) {
        Repp(j, m) {
            scanf("%c", &ch);
            if (ch == '.') {
                // 编号 
                dat[i][j] = ++ cnt;
                head[cnt].lc = head[cnt].rc = nll;
                
                if (cnt == 1) sx = i, sy = j;
            }
        }
        scanf("\n");
    }
}


void dfs(Pointer *x) {
    if (x == nll) return;
    dfs(x -> lc); 
    dfs(x -> rc);
    
    if (x -> rc != nll) 
        x -> lf = x -> rc -> lf + x -> lc -> get();
    else x -> lf = inf;
    
    if (x -> lc != nll) 
        x -> rf = x -> lc -> rf + x -> rc -> get();
    else x -> rf = inf;
    
    // 其实这里可以多记录两个部分和,那么不用每次往下找一次
    // 时间复杂度从 O(n ^ 3) 降到 O(n ^ 2) 
    int tmp = 1;
    Pointer *p = x;
    while (true) {
        p = p -> lc;
        if (p == nll) break;
        tmp += p -> rc -> get();
    }
    
    p = x;
    while (true) {
        p = p -> rc;
        if (p == nll) break;
        tmp += p -> lc -> get();
    }
    
    x -> lf = min(x -> lf, tmp);
    x -> rf = min(x -> rf, tmp);
}


pii que[maxp];


// 构建二叉树 
void Graph() {
    int quehead = 0, quetail = 0;
    que[0].st = sx, que[0].nd = sy;
    
    while (quehead <= quetail) {
        pii now = que[quehead ++];
        
        Rep(k, 4) {
            pii to;
            to.st = now.st + dx[k];
            to.nd = now.nd + dy[k];
            
            if (dat[to.st][to.nd] > 0) {
                Pointer *p = &head[dat[now.st][now.nd]];
                
                // 若已经有(左、右)孩子,则一直找(左、右)孩子的(左、右)孩子。 
                if (k < 2) {
                    while (p -> lc != nll) p = p -> lc;
                    p -> lc = &head[dat[to.st][to.nd]];
                }
                else {
                    while (p -> rc != nll) p = p -> rc;
                    p -> rc = &head[dat[to.st][to.nd]];
                }
                
                que[++ quetail] = to;
            }
        }
        
        dat[now.st][now.nd] = 0;
    }
}


int main() {
    freopen("bomb.in", "r", stdin);
    freopen("bomb.out", "w", stdout);
    
    Init();
    
    Graph();
    
    dfs(&head[1]);
    
    printf("%d\n", min(head[1].lf, head[1].rf));
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值