ZOJ3698 Carrot Fantasy 恶心模拟

题意:给定了一个塔防的游戏模型,跟着这个模型进行模拟,输出能在多少时间内消灭掉所有的怪兽,如果不能的话,输出-1。

解法:这里提几个要注意的地方:

1.注意怪兽不要走回头路;
2.注意已死的怪兽不能够再次成为防御塔选择或者是移动的对象;
3.所有防御塔是同时攻击的,因此选择型的防御塔不能够预判出该秒攻击是否多余,即最优目标是绝对的,不会因为其他塔能够射杀死中途改变;
4.要注意最后全部怪兽被持续冻结而不掉血的情况。

题目中可能没有把这个游戏的过程说的特别详细,对于每一秒钟是如下度过的:

在一秒钟开始的时候(S)所有的怪兽全部获得一秒钟更新时间:如果怪兽被冻结,那么这一秒钟用于解冻,无法移动;否则使用一秒钟向前移动一步。如果怪兽中毒的话,这一秒钟末(T)将掉一次血,同时在一秒末(T)获得所有炮塔的一次攻击选择。

由于所有的怪兽第一次出生无法移动,所以我们选择在第 i 秒末被炮塔攻击前出生,这样既能够被火力覆盖,又能够在下一秒移动了。对于是否被永久冻结的判定,只要是对游戏结束有推动作用的就认为没有陷入无限循环之中,包括有怪兽移动,怪兽中毒掉血,刚刷新出来的怪兽,被炮塔攻击后掉血,怪兽中毒这些。注意解冻和冻结不认为推动了游戏的结束,因为如果单单只是解冻和冻结游戏永远无法结束,而前面列举的动作只要有某个(有些状态是关联的,例如有一直怪兽中毒,肯定就会有中毒掉血,除非每次都被炮火无情摧残,不等到其中毒掉血,但这又使得怪兽中毒与其他相关联)连续作用,怪兽的数量是有限的,只要一直进行那么游戏总会以某种方式结束。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <cctype>
#include <algorithm>
using namespace std;

int sx, sy, ex, ey; // 用来表示怪兽起点和终点的坐标
int N, M, K, H;

struct monsters {
    int hp, x, y, step; // 分别表示怪兽的血量,坐标x,y,走了多少步
    int dir, fz, po; // 上一步的方向以及是否冻结或中毒
    
    void init() {
        hp = H, x = sx, y = sy, step = 0;
        fz = po = 0, dir = -1;
    }
};

struct tower {
    int id, x, y;
    int obj; // 用来记录每一时刻在攻击范围内的怪兽编号
    void init(int Id, int X, int Y) {
        id = Id, x = X, y = Y, obj = -1;
    }
};

int dir[4][2] = {0,1,0,-1,-1,0,1,0};
int ff[8][2] =  {0,1,0,-1,-1,0,1,0,1,1,1,-1,-1,1,-1,-1};
char mp[55][55];
monsters m[55];
tower t[55][55];

bool legal(int x, int y) {
    if (x < 1 || x > N || y < 1 || y > M) return false;
    return true;
}

int cmp(int a, int b) {
    if (a == -1) return b;
    if (b == -1) return a;
    if (m[a].step != m[b].step) {
        if (m[a].step > m[b].step) { // 走的步数多的怪兽被攻击的优先级高
            return a;
        } else return b;
    } else {
        if (a < b) { // 否则放回编号较小的怪兽
            return a;
        } else return b;
    }
}

/*
123
4 4 10 10
S...
XXI.
XXX.
T...

2 2 2 20
ST
FN
*/

bool over(int x) {
    if (x < K) return false; // 如果还有怪兽没有出生
    for (int i = 1; i <= x; ++i) {
        if (m[i].hp > 0) return false; // 如果还有怪物没有死亡
    }
    return true;
}

void solve() {
    int many = 0, ti;
    bool fail = false;
    for (ti = 1; !over(many) && !fail; ++ti) {
        bool change = false;
        for (int i = 1; i <= many; ++i) { // 首先给地图上的怪兽都分配一秒钟的时间 
            if (m[i].hp <= 0) continue;   // 如果这个怪兽已经死亡
            if (m[i].po) {
                m[i].hp -= 10;
                change = true; // 如果有怪兽中毒扣血,则说明有更新
                if (m[i].hp <= 0) {
                    continue;
                }
            }
            if (m[i].fz) {
                m[i].fz = 0; // 如果被冻结,则解冻,该秒钟无法行进
            }
            else { // 如果没有冻结则要前进一个距离
                m[i].step += 1; // 该怪兽前进的步伐加1
                change = true;  // 只要还在走,说明有更新 
                for (int j = 0; j < 4; ++j) {
                    if (j == m[i].dir) continue; // 保证它不能够往原路返回
                    int cx = m[i].x + dir[j][0], cy = m[i].y + dir[j][1];
                    if (legal(cx, cy) && (mp[cx][cy] == '.' || mp[cx][cy] == 'T')) { // 如果这个方向是正确的方向 
                        m[i].x = cx, m[i].y = cy, m[i].dir = j ^ 1;
                        break;
                    }
                }
                if (m[i].x == ex && m[i].y == ey) {
                    fail = true;
                    break;
                }
            }
        }
        if (fail) continue;
        if (ti <= K) {
        // 少于K的时间内,每秒刷新一个新怪兽,且该怪兽该秒钟无法移动,但是能够成为攻击目标
            m[ti].init();
            change = true;
        }
        many = min(ti, K);
        for (int i = 1; i <= many; ++i) { // 选择用怪兽匹配炮塔,因为炮塔是固定且不重叠
            if (m[i].hp <= 0) continue; // 已经死亡的不予考虑,这个死亡是以前的攻击造成的
            for (int j = 0; j < 8; ++j) { // 在这8次匹配中可能中途死亡,由于是同时攻击,所以不用中途退出 
                int cx = m[i].x + ff[j][0], cy = m[i].y + ff[j][1];
                if (legal(cx, cy) && isdigit(mp[cx][cy])) { // 该怪兽在某个炮塔的火力覆盖范围内
                    if (t[cx][cy].id != 2) {
                        t[cx][cy].obj = cmp(t[cx][cy].obj, i); // 选择性炮塔只要选择一个最优的进行攻击
                    } else { // 如果是Fire Tower,只要在其火力覆盖内,则直接掉血 
                        m[i].hp -= 10;
                        change = true;
                    }
                }
            }
        }
        for (int i = 1; i <= N; ++i) {
            for (int j = 1; j <= M; ++j) {
                if (t[i][j].id && t[i][j].id != 2 && t[i][j].obj != -1) { // 如果该地点有炮塔
                    if (t[i][j].id == 1) {
                        change = true;
                        m[t[i][j].obj].hp -= 10;
                    }
                    else if (t[i][j].id == 3) {
                        change = true;
                        // 有中毒就又希望,如果一直有怪兽中毒,不管它之前有没有中毒,如果到不了终点,它最终将死掉 
                        m[t[i][j].obj].po = 1;
                    }
                    else {
                        // 被冻结或者解冻不能算是有更新,因为单纯的这个过程怪兽没有掉血也没有移动 
                        m[t[i][j].obj].fz = 1;
                    }
                    t[i][j].obj = -1; // 将对象释放
                }
            }    
        }
        if (!change) fail = true;
    }
    if (fail) puts("-1");
    else printf("%d\n", ti - 1);
}

int main() {
    int T;
    for (scanf("%d", &T); T; --T) {
        scanf("%d %d %d %d", &N, &M, &K, &H);
        for (int i = 1; i <= N; ++i) {
            scanf("%s", mp[i]+1);
            for (int j = 1; j <= M; ++j) {
                t[i][j].id = 0; // 默认该地点没有炮塔
                if (mp[i][j] == 'X' || mp[i][j] == '.') continue;
                else if (mp[i][j] == 'B') mp[i][j] = '1', t[i][j].init(1, i, j);
                else if (mp[i][j] == 'F') mp[i][j] = '2', t[i][j].init(2, i, j);
                else if (mp[i][j] == 'N') mp[i][j] = '3', t[i][j].init(3, i, j);
                else if (mp[i][j] == 'I') mp[i][j] = '4', t[i][j].init(4, i, j);
                else if (mp[i][j] == 'S') sx = i, sy = j;
                else ex = i, ey = j;
            }
        }
        solve();
    }
    return 0;    
} 
原文地址:https://www.cnblogs.com/Lyush/p/3069345.html