BZOJ2548:[CTSC2002]灭鼠行动
我对模拟的理解:https://www.cnblogs.com/AKMer/p/9064018.html
题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=2548
原本计划先在三国杀赢了那几头猪再来灭这群老鼠的……
然后因为被猪吊锤了一周……然后特工队也等不下去了,我就被调来先灭老鼠了……
灭完老鼠继续跟猪打三国杀去\(emmm\)……
\([SDOI2010]\)猪国杀和\([CTSC2002]\)灭鼠行动就是我模拟旅程的最后两站了,弄完就不专门搞模拟了。
由于有[ZJOI2008]杀蚂蚁这题作为前提,我对于这种在地图上杀什么玩意儿的题理解深了很多,所以写灭鼠行动的时候比杀蚂蚁轻松多了。很多人说灭鼠行动比杀蚂蚁难,我觉得并不然。
首先,让我们把研究的重点放在炸弹上:
炸弹放下即爆,或者直接晕和变性(听起来好变态)
定时炸弹相当于一个即爆炸弹,我们可以假装它其实是t+3才放下来的。
然后就没什么别的了。
然后我们再把目光放到老鼠身上来:
老鼠移动算法比蚂蚁移动算法简单多了,能往前就往前,不然就拐弯,拐弯判一判就没了。
然后繁殖的时候要注意,只有在某个格子上只有一雄一雌两只成年老鼠才会生仔(他们需要私人空间,有其它老鼠在是不会繁殖的)
然后繁殖以及休息相当于眩晕三秒,第二秒后小老鼠就可以出来了。
如果在生产过程中被眩晕,那么小老鼠也要推迟时间出来。
怎么都觉得比杀蚂蚁简单多了……唉,不说了,我跟猪打三国杀去了。
时间复杂度:\(O(Tnm)\)
空间复杂度:\(O(不会炸)\)
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define sqr(x) ((x)*(x))
const int inf=2e9;
char s[10];//读入字符串用
bool explode[55][55];//explode[x][y]表示(x,y)是否在爆炸范围内
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};//四个方向,分别是北,东,南,西,对应编号0,1,2,3,刚好对应读入代表方向的数字2^0,2^1,2^2,2^3
int left[4]={3,0,1,2};//left[x]表示x方向的左边方向编号
int right[4]={1,2,3,0};//right[x]表示x方向的右边编号
int fake[4]={1,2,4,8};//fake[x]表示2^x,也就是读入的网格信息里表示方向的数字
int now_time,now_bomb=1;//now_time表示现在时间,now_bomb表示现在轮到哪一个炸弹了
int con[55][55],mice_sum[55][55];//con[x][y]记录(x,y)与四周的连通性,mice_sum[x][y]记录(x,y)上一共多少老鼠
int len,radius,n,m,tot,limit,Time,cnt;//len记录1号炸弹范围,radius记录2号炸弹范围半径,n,m记录地图大小,tot实时更新记录老鼠个数,limit为评判鼠疫是否爆发的标准,Time是要求模拟的时间,cnt是炸弹个数
int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}//快读
struct mouse {
bool dead,turn_left;
int x,y,dir,sex,ver,bre,grow;
//dead记录老鼠是否死亡,turn_left记录老鼠碰到前方无路左右都有路是否要往左转,x,y记录老鼠坐标,dir记录方向,sex记录性别,ver记录眩晕时间,bre记录小老鼠出生的时间,grow记录还有多久成年
void build() {
x=read(),y=read();
scanf("%s",s);
if(s[0]=='N')dir=0;
if(s[0]=='E')dir=1;
if(s[0]=='S')dir=2;
if(s[0]=='W')dir=3;
turn_left=1;//奇数次往左拐,所以初始为true
scanf("%s",s);
if(s[0]=='X')sex=1;
bre=-1;
}//读入老鼠信息
void move() {
bre=-1;//能移动了说明没有预产期,把预产期恢复成-1
if(con[x][y]&fake[dir]) {
x+=dx[dir],y+=dy[dir];
return;//如果能在原方向上向前走就向前走
}
if(fake[left[dir]]&con[x][y]) {//如果能向左走
if(fake[right[dir]]&con[x][y]) {//如果能向右走
if(turn_left)dir=left[dir];
else dir=right[dir];//奇数次向左,偶数次向右
turn_left^=1;//改变下一次拐弯的方向
}
else dir=left[dir];//只能向左走,花一单位时间转向
return;
}
if(fake[right[dir]]&con[x][y]) {
dir=right[dir];
return;//只能向右走,花一单位时间转向
}
dir=left[dir];//掉头相当于往左转两次
}
}M[500];
struct bomb {
int type,tim,x,y;
//type记录炸弹类型,tim记录炸弹爆炸时间,x,y记录炸弹坐标
bool operator<(const bomb &a)const {
return tim<a.tim;
}//按时间排序
void build() {
type=read(),tim=read();
x=read(),y=read();
if(type==3)tim+=3;//如果是三号炸弹那么时间推迟3个单位
}
void mark_1(int x,int y) {//标记1号炸弹爆炸范围
explode[x][y]=1;//炸弹所在点标记
for(int i=0;i<4;i++) {//往四个方向走
int now_x=x,now_y=y,step_sum=0;
//step_sum表示从(x,y)到(now_x,now_y)距离多少
while(step_sum<len&&(fake[i]&con[now_x][now_y]))
now_x+=dx[i],now_y+=dy[i],explode[now_x][now_y]=1,step_sum++;//如果能继续深入就继续深入
}
}
void mark_2(int x,int y) {//2号炸弹标记范围
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(sqr(i-x)+sqr(j-y)<=radius)
explode[i][j]=1;//勾股定理
}
void blast() {
memset(explode,0,sizeof(explode));//清空标记
if(type==1)mark_1(x,y);
if(type==2)mark_2(x,y);
if(type==3)explode[x][y]=1;
if(type==4)explode[x][y]=1;//标记范围
for(int i=1;i<=tot;i++)
if(explode[M[i].x][M[i].y]) {//如果在范围内
if(type==1||type==3)M[i].dead=1;//如果是1号或者3号炸弹直接秒了老鼠
if(type==2) {
M[i].ver+=3;
if(M[i].bre>now_time)M[i].bre+=3;//2号炸弹就加眩晕,如果有预产期预产期也跟着推后
}
if(type==4)M[i].sex^=1;//4号炸弹变性
}
}
}B[105];
void check_dead() {
int top=0;
for(int i=1;i<=tot;i++)
if(!M[i].dead)
M[++top]=M[i];
tot=top;//清理死亡老鼠尸体
}
void breed() {
memset(mice_sum,0,sizeof(mice_sum));//清空mice_sum[][]
for(int i=1;i<=tot;i++)
mice_sum[M[i].x][M[i].y]++;//更新mice_sum[][]
for(int i=1;i<=tot;i++)
if(!M[i].ver&&M[i].bre==-1&&!M[i].grow&&M[i].sex)//如何i号老鼠没有眩晕时间并且可以繁衍,也是成年的雄老鼠
for(int j=1;j<=tot;j++)
if(!M[j].ver&&M[j].bre==-1&&!M[j].grow&&!M[j].sex)
//j号老鼠也是没有眩晕的成年可生育老鼠,并且是雌的 if(mice_sum[M[i].x][M[i].y]==2&&M[i].x==M[j].x&&M[i].y==M[j].y){//如果他们在同一个格子上并且没有其它老鼠在场就开始造老鼠
M[i].ver+=3;M[j].ver+=3;//眩晕时间+3
M[i].bre=M[j].bre=now_time+2;//2单位时间后生出小老鼠
break;
}
for(int i=1;i<=tot;i++)
if(M[i].bre==now_time&&M[i].sex)//如果现在的时间这只老鼠会生育并且他是雄的,每一对老鼠生育只算一次
for(int j=0;j<4;j++)
if(fake[j]&con[M[i].x][M[i].y]) {//如果该点有该方向的管道那么生成一只小老鼠
tot++;
M[tot].x=M[i].x;
M[tot].y=M[i].y;
M[tot].ver=0;
M[tot].dir=j;
M[tot].grow=5;
M[tot].bre=-1;
M[tot].dead=0;
M[tot].turn_left=1;
if(j==0||j==2)M[tot].sex=1;
else M[tot].sex=0;
//记得所有数据全部都要初始化,因为tot+1保不定存了某只死亡的老鼠的信息,不全部初始化就GG了
}
}
int main() {
len=read(),radius=read(),n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
con[i][j]=read();
tot=read();radius*=radius;//读入,半径先平方一遍,到时候直接上勾股定理
for(int i=1;i<=tot;i++)
M[i].build();//读入老鼠信息
cnt=read(),limit=read();
for(int i=1;i<=cnt;i++)
B[i].build();//读入炸弹
sort(B+1,B+cnt+1);B[cnt+1].tim=inf;//炸弹排序
Time=read();//读入时间
for(now_time=0;now_time<=Time;now_time++) {
while(B[now_bomb].tim==now_time)
B[now_bomb].blast(),now_bomb++;//把现在这个时间会炸的炸弹点爆。每一秒最先发生的事情是炸炸弹而不是老鼠繁殖和移动,这一点我改了好久……把它当杀蚂蚁了……
check_dead();//清理尸体
breed();//繁殖
if(tot>limit) {puts("-1");return 0;}//如果鼠疫爆发就直接没得玩了
if(now_time==Time)break;//时间过完了就不移动了。
for(int i=1;i<=tot;i++) {
if(M[i].ver)
M[i].ver--;//如果眩晕就减一秒眩晕时间
else {
M[i].move();//移动
if(M[i].grow)M[i].grow--;//小老鼠长大一秒
}
}
}
printf("%d\n",tot);//输出老鼠总数
return 0;
}