题解 P5506 【封锁】

· · 题解

一道大模拟题

先吐槽一下吧:

这是一个比赛的T1

比赛前,出题人说:“这次比赛难度 普及- 到 省选 难度吧,T1是最简单的。” 我自信满满的点开第一道题,瞬间就蒙了。

你告诉我这题 普及-???

言归正传

听说有人打这道题500+行的代码 awa,我这里给出一个80行的代码题解吧。毕竟代码短,可读性较高,而且我对我的码风很自信。题解已经尽力写的很详细了,绝对的逐步求解,希望能帮到大家。

再怎么说,也是一道T1的题。一道大模拟,完全可做,思维性不高,就是细节很多,需要逐步把控。

所以我们一步一步来~,做这种题不能急。

这道题共有7种操作,每驾无人机共包含12种信息(各个数值和操作)

于是,这里开结构体一定是最稳健的。不管后面有没有什么排序一类的操作,打包一堆数据总比一堆零散的数据容错率要高一些。

struct feiji
{
    int x, y, z, h, f, atk, deg, mat, mdf, hp, fix, ti;//ti代表飞机的号数。
    string s;//代表操作
}f[205];

另外,我在比赛上想过开二维数组来模拟飞机的位置状况,但是坐标是有负值的。而且在我强行把坐标加上一个较大值,不让坐标为负时,事实证明还是会炸,其实是我太蒟了)。所以我们不选择开数组模拟位置。

每驾存活的无人机,每回合都会移动一轮。而移动方向则是他们面向的方向。

这里需要注意:所有飞机都移动完之后,各个飞机才会执行各个指令(如转向,攻击)

既然我们刚刚把方向的表给打出来了,移动就变得很简单了。

for(int j = 1; j <= n; j ++)//枚举每驾飞机
{
    if(f[j].hp > 0)//存活的飞机才能移动
    {
        f[j].x += dx[f[j].f][f[j].h]; //向自己的方向移动
        f[j].y += dy[f[j].f][f[j].h];
        f[j].z += dz[f[j].f][f[j].h];
    }
}

由于我们存储移动方向的单位 正好是前方一格,所以直接加在原坐标上即可。

有个表是不是很方便?

转向是一个大坑,甚至出题人自己都弄错了。

最开始这题是没有f/h表的,很多人反馈说转向看不懂是什么意思,我也看不懂。出题人就弄了一个表。

然后出题人还把表打错了,后来才改回来

既然有了表,就很简单了。我们把表打上去就可以了。 也用不着写函数计算什么之类的,这样代码复杂,写错了debug也不方便

int dx[8][5] = {{0, 1, 1, 1, 0},{0, 1, 1, 1, 0},{0, 0, 0, 0, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0},{0, 0, 0, 0, 0},{0, 1, 1, 1, 0}};
int dy[8][5] = {{0, 0, 0, 0, 0},{0, 1, 1, 1, 0},{0, 1, 1, 1, 0},{0, 1, 1, 1, 0},{0, 0, 0, 0, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0}};
int dz[8][5] = {{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1}};

其中dx[f][h],dy[f][h],dz[f][h]分别表示,飞机面向方向 前面的一个格子 的x,y,z坐标。(听起来很绕,多读几遍就好了)

于是,只要有了无人机的f,h值,我们就可以知道它面向的方向。

表打好了,接下来还有修改操作。即修改无人机的f,h值。

在这里,h值是有上下界的。 譬如h=4时,U指令无效,h=0时,D指令无效。

f值则不同,它的数据构成了一个环。f=0时执行L,会使f=1,但是会若执行R,则会变为f=7。当然f=7时,执行L也会使f=0所以说,这里的0,1,2,...,7构成了一个环,我们实际是在这个环上做着L,R操作

对于这两个值的处理,特判就可以了。

//这里,我用char型字符c储存的指令信息,后面也一样。
if(c == 'U') if(f[j].h < 4) f[j].h ++;
if(c == 'D') if(f[j].h > 0) f[j].h --;
if(c == 'L')
{
    f[j].f ++;
    if(f[j].f > 7) f[j].f = 0;
}
if(c == 'R')
{
    f[j].f --;
    if(f[j].f < 0) f[j].f = 7;
}//这组代码没什么好说的,看就可以看懂

这个是最简单的操作了,对自己的飞机hp值加上fix值就可以了

题目没说什么生命条上限之类的东西,不用特判。

if(c == 'F') f[j].hp += f[j].fix;

接下来才是重头戏! 两个攻击操作是全题最难的点了。

方法Ⅰ、

这种方法也是我用的方法。

我们现在来想象一下:

无人机发出了子弹/激光。

无人机 面向方向的前面一格就会出现一颗 子弹/激光束。(必须是前面一格,因为打不到与自己重叠的飞机)

子弹保持无人机面向的方向飞去 / 激光束保持无人机面向的方向扩散。

我们现在使用慢放的高超技术,我们可以看到每一时刻,子弹 / 激光束每次以原来的方向移动一格.....

直到碰到了飞机,子弹消失 / 激光束穿了过去,继续向前飞去....

于是乎,在想象中,我们的算法就完成了:记录无人机位置的前面一格,使用 for循环一直往下找(每次向无人机面向的方向移动一格),直到遇到了无人机,子弹消失(break掉),激光继续行驶。

怎么判断攻击是否击中了其他飞机呢??由于我太蒟了,只能用一种笨拙的方法:

我们在每次子弹/激光束移动完成后,就枚举一遍所有存活的飞机,看是否有飞机的坐标正好与 子弹/激光束 的坐标相同。相同的话就说明打到飞机了,造成伤害即可。

可以看到,这种方法十分的笨拙,但好在本题数据不大,最高也就开到了100。所以是题目是可以AC的。

代码:

if(c == 'A')
{
    bool flag = false;
    for(int k = 1; k <= 100; k ++)//循环,模拟子弹/激光束移动
    {
        for(int v = 1; v <= n; v ++)//枚举每驾飞机,看是否被攻击。
            if(f[j].x + dx[f[j].f][f[j].h] * k == f[v].x && f[j].y + dy[f[j].f][f[j].h] * k == f[v].y && f[j].z + dz[f[j].f][f[j].h] * k == f[v].z && f[v].hp != 0) //子弹遇上飞机,且那架飞机存活,就减血
            {
                if(f[j].atk > f[v].deg)f[v].hp -= (f[j].atk - f[v].deg);
                flag = true;//由于子弹会消失,所以要及时break;
                break;
            }
        if(flag) break;
    }   
}
if(c == 'M')
{
    for(int k = 1; k <= 100; k ++)
        for(int v = 1; v <= n; v ++)
            if(f[j].x + dx[f[j].f][f[j].h] * k == f[v].x && f[j].y + dy[f[j].f][f[j].h] * k == f[v].y && f[j].z + dz[f[j].f][f[j].h] * k == f[v].z) //激光遇上飞机,且那架飞机存活
        if(f[j].mat > f[v].mdf)f[v].hp -= (f[j].mat - f[v].mdf);        
}

提一句:我的判断里写的是:

f[j].x/y/z + dx/y/z[f[j].f][f[j].h] * k == f[v].x/y/z

其实这也是很好理解的,把累加换成了乘法而已。因为加上的数是相同的。

这就和 1+1+1+1+11*5 相同 是一个道理。

顺带吐槽一句:这道题 k 最小开到 8 居然都能过去,也就是说我的子弹和激光射程为 8都能AC,这数据是有多 好。

方法Ⅱ、

方法Ⅰ时间复杂度不稳,很悬?怎么办?

这里给出方法Ⅱ,这需要一颗数学的大脑。

我们可以求出无人机面向方向的直线方程式(就是所谓的 y = kx + bAx+By+C = 0

拿这个方程式去代入所有飞机的坐标,如果在这条直线上,那么就会被攻击到。

这里倒是有一个前提:必须是攻击发起者无人机面对的方向。 毕竟背对无人机方向的飞机也有可能在这条直线上,所以要特判。

如果是激光,上述所说的就已经完成操作了。但子弹遇到第一架飞机会消失,这里就比较麻烦了。

我们需要计算所有满足上述要求无人机 与 攻击发起者 的距离。距离最短的就是被攻击的目标了。

由于这种方法是省下了时间,但是代码很容易出错,我也就没去用这种方法。当然也没有代码。有自信的童鞋们可以去试试 awa。

6、合并

上述就是所有操作的讲解及代码了。

(请不要问我为什么无视要N操作,什么都不操作那就不叫操作了)

由于在所有飞机移动之后,剩下的飞机就会按编号顺序,每次执行一个指令。所以上述的指令代码全部都套在一个for循环中,这个for循环枚举了每驾飞机。

最后的最后,由于时间为t,所以上述的 移动 + 各个操作 都需要放在一个大循环内

于是我们把代码合并下来。

#include<bits/stdc++.h>
#define mian main
#define QWQ puts("QWQ");
using namespace std;

struct feiji
{
    int x, y, z, h, f, atk, deg, mat, mdf, hp, fix, ti;
    string s;
}f[205];

int n, t;
int dx[8][5] = {{0, 1, 1, 1, 0},{0, 1, 1, 1, 0},{0, 0, 0, 0, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0},{0, 0, 0, 0, 0},{0, 1, 1, 1, 0}};
int dy[8][5] = {{0, 0, 0, 0, 0},{0, 1, 1, 1, 0},{0, 1, 1, 1, 0},{0, 1, 1, 1, 0},{0, 0, 0, 0, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0},{0, -1, -1, -1, 0}};
int dz[8][5] = {{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1},{-1, -1, 0, 1, 1}};

int main()
{
    scanf("%d%d", &n, &t);//输入部分 
    for(int i = 1; i <= n; i ++)
    {
        scanf("%d%d%d%d%d%d%d%d%d%d%d", &f[i].x, &f[i].y, &f[i].z, &f[i].h, &f[i].f, &f[i].atk, &f[i].deg, &f[i].mat, &f[i].mdf, &f[i].hp, &f[i].fix);
        cin >> f[i].s;
        f[i].ti = i;//这里记录了每驾无人机的编号 
    } 
    for(int i = 1; i <= t; i ++)//t表示时间,所以要循环t轮 
    {
        for(int j = 1; j <= n; j ++)
        {
            if(f[j].hp > 0)
                f[j].x += dx[f[j].f][f[j].h], f[j].y += dy[f[j].f][f[j].h], f[j].z += dz[f[j].f][f[j].h];
        }
        for(int j = 1; j <= n; j ++)
        {
            if(f[j].hp <= 0)//坠毁的飞机就不操作了
                continue;
            char c = f[j].s[i - 1];
        //由于字符串是从 s[0]开始存的(而不是1),所以这里要 -1。
            if(c == 'N')continue;//没有操作就continue掉
            if(c == 'U') if(f[j].h < 4) f[j].h ++;
            if(c == 'D') if(f[j].h > 0) f[j].h --;
            if(c == 'L')
            {
                f[j].f ++;
                if(f[j].f > 7) f[j].f = 0;
            }
            if(c == 'R')
            {
                f[j].f --;
                if(f[j].f < 0) f[j].f = 7;
            }
            if(c == 'F') f[j].hp += f[j].fix;
            if(c == 'A')
            {
                bool flag = false;
                for(int k = 1; k <= 100; k ++)
                {
                    for(int v = 1; v <= n; v ++)
                        if(f[j].x + dx[f[j].f][f[j].h] * k == f[v].x && f[j].y + dy[f[j].f][f[j].h] * k == f[v].y && f[j].z + dz[f[j].f][f[j].h] * k == f[v].z && f[v].hp != 0) 
                        {
                            if(f[j].atk > f[v].deg)f[v].hp -= (f[j].atk - f[v].deg);
                            flag = true;
                            break;
                        }
                    if(flag) break;
                }   
            }
            if(c == 'M')
            {
                for(int k = 1; k <= 100; k ++)
                    for(int v = 1; v <= n; v ++)
                        if(f[j].x + dx[f[j].f][f[j].h] * k == f[v].x && f[j].y + dy[f[j].f][f[j].h] * k == f[v].y && f[j].z + dz[f[j].f][f[j].h] * k == f[v].z)
                            if(f[j].mat > f[v].mdf)f[v].hp -= (f[j].mat - f[v].mdf);        
            }
        }
    }
    for(int i = 1; i <= n; i ++)
    {
        printf("%d %d %d ", f[i].x, f[i].y, f[i].z);
        if(f[i].hp <= 0) printf("0\n");//如果坠毁,生命值输出0
        else printf("%d\n", f[i].hp);
    }
    return 0;
} 

看代码好像很长...其实我觉得这个算短的了。

7、注意点:

8、后记

不得不说啊...这种大模拟打起来真的很累人。但是多练这种题,代码能力会明显提高。

毕竟套算法的就是考你算法学习,套模拟就是考你代码能力。

这篇题解总计码了1h30min。写的算是比较细了,希望大家能多多支持(点赞)。希望大家能看懂 QAQ。

有不懂的可以发评论 或者 讨论版 at我,我尽量会看的 awa。