灭鼠行动 题解

TKXZ133's Blog / 2023-08-07 / 原文

灭鼠行动

前言

  • 只能保证此题解中的代码能通过本题的所有数据,不保证一定能通过所有符合题目给出条件的数据。实在不想调了。

  • 另一份题解中的数据生成器是错的,会给出一些不合法的地图。(比如地图中有 \(17\)\(25\) 之类的数)

  • 本题数据水,一些有比较大的错误的代码也能过。(我的代码没加下文中的第二条细节的特判也能过)

  • 我把我的代码和另一份题解中的代码放在一起拍了 \(1000\) 组数据,总共出现了十来组不一样的答案,原因主要在昏迷结束后同一位置的老鼠是否能立刻繁殖的问题上,两份代码不一样(我的代码允许,另一份代码会先移动),此处存疑。

思路分析

按照题意模拟即可。

主要看代码。

亿些细节

  • 每个时刻的优先级应为:武器 > 繁殖 > 出生 > 行动。

  • 如果两只老鼠在繁殖期间被炸死,小老鼠不会出生。

  • 如果两只老鼠在繁殖期间被转化性别,繁殖过程不变。

  • 同一位置有恰好两只老鼠才会考虑繁殖。

  • 遇到岔路口时先累加遇到的次数再考虑转向。

  • 死胡同只需要转一次就变成了单向岔路的情况。

  • 考虑繁殖时需要判断两只老鼠是否都满足所有条件。

  • 繁殖休息结束后需要动一下,移动转向都行,但必须动一下。

  • 注意时间单位和时刻的端点开闭性。

  • 强力炸弹的范围是一个十字,不能穿墙,可以取到 \(L\)

  • 神秘射线的范围是一个圆形,可以穿墙,可以取到 \(R\)

  • 如果你使用 vector,删除老鼠时注意指针会不会失效。

  • 计算时间的优先级应为:昏迷 > 休息 = 成长。

  • 注意在生成老鼠和删除老鼠时更新老鼠的数量。

  • 不能逐一对每只老鼠进行繁殖和移动判定,应先对所有老鼠走一遍繁殖判定,再对所有老鼠走一遍移动判定。

代码

不加注释只有 5.1k,应该算短的了。

注释写的比较详细,可以看一看。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>

using namespace std;
const int N=200,T=1010;
#define left(x) (((x)+3)%4)//x 的左方向
#define right(x) (((x)+1)%4)//x 的右方向

int L,R,m,n,K,P,Limit,EndTime,Time;//Time 表示当前时刻,其他的是题目给的
int MouseNum,MouseId,in1,in2,in3,in4;//当前老鼠的数量与编号
int road[N][N][4];//0-N北,1-E东,2-S南,3-W西
//road[x][y][z] 表示 (x,y) 的 z 方向能否通行
int vx[]={-1,0,1,0},vy[]={0,1,0,-1},se[]={0,1,0,1};
//vx[i] 表示向 i 方向移动 x 坐标的变化量,vy[i] 同理
//se[i] 表示 i 方向的性别

struct Mouse{
    int x,y;//坐标
    int dir;//方向
    int sex;//性别 0-X雄,1-Y雌
    int coma;//停止昏迷时刻
    int cnt;//第几次遇到转向情况
    int adu;//成年还需的时间
    int rel;//剩余休息的时间
    int move;//是否需要移动再繁殖
}NewM,mo[N];//mo 中储存所有存活过的老鼠,mouse 中储存所有当前存活的老鼠编号

struct Weapon{
    int type;//类型 1-强力炸弹,2-神秘射线,3-定时炸弹,4-生物炸弹
    int tim;//放置时刻
    int x,y;//放置坐标
}we[N];

char inp[5];
vector<int> weatime[T];//weatime[i] 储存 i 时刻需要启动的武器编号
vector<int> mouse,mp[N][N];//mp[x][y] 储存 (x,y) 位置的所有老鼠,这样写起来比较方便,常数也会小一点(?)
vector<int> appear[T];//appear[i] 储存 i 时刻出生的老鼠编号

void bomb(int x,int y,int r){//在 (x,y) 位置爆炸一个半径为 r 的十字炸弹,这里把两种炸弹的爆炸合并了,若是强力炸弹 r=L,若是定时炸弹 r=0
    for(int i=0;i<4;i++)//四个方向
        for(int j=0;j<=r;j++){
            int xx=x+vx[i]*j,yy=y+vy[i]*j;
            if(xx<1||yy<1||xx>m||yy>n) break;//超出地图边界
            if(j&&!road[xx-vx[i]][yy-vy[i]][i]) break;//穿过墙壁
            for(auto it:mp[xx][yy])
                mouse.erase(find(mouse.begin(),mouse.end(),it));//删除编号为 it 的老鼠
            for(int t=Time;t<=Time+2;t++)//删除接下来此处会出生的老鼠
                for(int id=0;id<appear[t].size();){
                    int now=appear[t][id];
                    if(mo[now].x==xx&&mo[now].y==yy)
                        appear[t].erase(find(appear[t].begin(),appear[t].end(),now));
                    else id++;//注意删除的方法
                }
            MouseNum-=mp[xx][yy].size();//更新老鼠数量
            mp[xx][yy].clear();//清空此处老鼠
        }
}

void coma(int x,int y){//在 (x,y) 位置放置神秘射线
    for(auto it:mouse)
        if((mo[it].x-x)*(mo[it].x-x)+(mo[it].y-y)*(mo[it].y-y)<=R*R){//检测半径
            if(mo[it].coma>=Time) mo[it].coma+=3;
            else mo[it].coma=Time+3;//更新昏迷时间,这里写麻烦了
        }
}

void reve(int x,int y){//在 (x,y) 位置放置生物炸弹
    for(auto it:mp[x][y]) mo[it].sex^=1;//反转性别
}

void move(int id){//移动编号为 id 的老鼠
    int &dir=mo[id].dir,&x=mo[id].x,&y=mo[id].y;
    if(road[x][y][dir]){//首先直行
        mp[x][y].erase(find(mp[x][y].begin(),mp[x][y].end(),id));
        x+=vx[dir];y+=vy[dir];//更新坐标
        mp[x][y].push_back(id);//别忘了更新 mp
    }
    else if(road[x][y][left(dir)]&&road[x][y][right(dir)]){//岔路口
        mo[id].cnt++;//先加
        if(mo[id].cnt&1) dir=left(dir);//判断左右移动
        else dir=right(dir);
    }
    else if(road[x][y][left(dir)]||road[x][y][right(dir)]){//单边
        if(road[x][y][left(dir)]) dir=left(dir);
        else dir=right(dir);
    }
    else dir=right(dir);
}

void birth(int id){//繁殖
    if(mo[id].adu||mo[id].rel||mo[id].move) return ;//判断自己的情况
    int x=mo[id].x,y=mo[id].y;
    if(mp[x][y].size()!=2) return ;//恰好两只
    for(auto it:mp[x][y]){
        if(it==id) continue;
        if(mo[it].adu||mo[it].sex==mo[id].sex) continue;//判定合法
        if(mo[it].coma>Time||mo[it].rel||mo[it].move) continue;//判定对方情况
        mo[id].rel=4;mo[it].rel=4;//进入休息,设为 4 是因为这是右开的
        for(int i=0;i<4;i++)//向四个方向生成老鼠
            if(road[x][y][i]){
                MouseId++;
                mo[MouseId]=Mouse{x,y,i,se[i],0,0,5,0};//小老鼠的初始化
                appear[Time+2].push_back(MouseId);
            }
        break;
    }
}

int main(){
    scanf("%d%d%d%d",&L,&R,&m,&n);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++){
            scanf("%d",&in1);
            if(in1>=8) road[i][j][3]=1,in1-=8;
            if(in1>=4) road[i][j][2]=1,in1-=4;
            if(in1>=2) road[i][j][1]=1,in1-=2;
            if(in1>=1) road[i][j][0]=1,in1-=1;//读入并生成地图
        }
    scanf("%d",&K);
    for(int i=1;i<=K;i++){//生成老鼠
        scanf("%d%d",&in1,&in2);
        NewM.x=in1;NewM.y=in2;
        scanf("%s",inp+1);
        if(inp[1]=='N') NewM.dir=0;
        if(inp[1]=='E') NewM.dir=1;
        if(inp[1]=='S') NewM.dir=2;
        if(inp[1]=='W') NewM.dir=3;//判定方向
        scanf("%s",inp+1);
        if(inp[1]=='X') NewM.sex=0;
        if(inp[1]=='Y') NewM.sex=1;//判定性别
        mouse.push_back(i);//更新老鼠所在
        mp[in1][in2].push_back(i);
        mo[i]=NewM;
    }
    MouseNum=MouseId=K;
    scanf("%d%d",&P,&Limit);
    for(int i=1;i<=P;i++){
        scanf("%d%d%d%d",&in1,&in2,&in3,&in4);
        we[i].type=in1;we[i].tim=in2;
        we[i].x=in3;we[i].y=in4;
        weatime[in2].push_back(i);//更新武器所在
    }
    scanf("%d",&EndTime);
    for(Time=0;Time<=EndTime;Time++){//开始模拟
        for(auto &i:weatime[Time]){//启动 1,2,4 类武器
            if(we[i].type==1) bomb(we[i].x,we[i].y,L);
            if(we[i].type==2) coma(we[i].x,we[i].y);
            if(we[i].type==4) reve(we[i].x,we[i].y);
        }
        for(auto &i:weatime[max(0,Time-3)])//启动 3 时刻前的定时炸弹
            if(we[i].type==3) bomb(we[i].x,we[i].y,0);
        
        for(auto it:mouse){//判定繁殖
            if(mo[it].coma>Time||mo[it].rel||mo[it].move) continue;
            birth(it);
        }
        for(auto it:appear[Time]){//判定生成
            mouse.push_back(it);
            mp[mo[it].x][mo[it].y].push_back(it);//记得加入老鼠所在位置,不然会 RE
            MouseNum++;
        }
        for(auto it:mouse){//更新休息时间
            if(mo[it].coma>Time) continue;
            if(mo[it].rel){
                mo[it].rel--;
                if(!mo[it].rel) mo[it].move=1; 
                continue;
            }
        }
        for(auto it:mouse){//判定移动
            if(mo[it].coma>Time||mo[it].rel) continue; 
            move(it);mo[it].move=0;
            if(mo[it].adu) mo[it].adu--;//更新年龄
        }
        if(MouseNum>Limit){cout<<"-1\n";return 0;}//发生鼠疫
    }
    cout<<MouseNum<<'\n';
    return 0;
}

其他

2022 年 12 月 23 日 16:40:随机跳题 ing。

2022 年 12 月 23 日 16:50:咦?这道题好有意思,写写看。

2022 年 12 月 23 日 20:30:我写完了,开调。

2022 年 12 月 23 日 21:30:20pts 调不出来了,算了。

2023 年 8 月 6 日 15:10:随机跳题 ing。

2023 年 8 月 6 日 15:20:咦?这道题我怎么见过,开写,看我什么时候写完。

2023 年 8 月 6 日 18:10:我写完了,开调。

2023 年 8 月 7 日 9:20:我调完了。