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;
}
posted @ 2018-06-11 09:49  AKMer  阅读(315)  评论(0编辑  收藏  举报