1070: 炸僵尸
时间限制: 1 Sec 内存限制: 128 MB
题目描述
妈妈得知小星星成功地解决了买玩具难题,奖励他去看电影《生化危机 6》,小星星看着看着就替女主角担心起来了,因为她要对付那么多的僵尸怪物,小星星恨不得扔颗炸弹消除可恶的僵尸们,他脑袋里开始构思出这样的场景:
在一个 N 行 M 列单元格构成的地图中,去放置一个炸弹,这种炸弹威力巨大,以放置点为中心进行行列延伸炸到同行同列的僵尸,但不能穿墙。上图中可以把炸弹放置在第 3 行第 4 列,最多可以炸到 4 个僵尸,如果对地图稍加改动(如下图),在第 5 行第 4 列处加
入一个墙体,又如何呢?答案还是最多炸到 4 个僵尸,只不过最佳炸弹放置点发生了变化,应该放到第 6 行第 6 列的位置。
当然炸弹要靠勇敢的小星星去放,他只能在地图中朝上下左右四个方向行进(不能斜对
角移动),他不能穿墙,也不能穿越僵尸,要保证他的安全,如下图,告诉你小星星起始位置是第 2 行第 2 列,那么他的最佳放置炸弹位置应该是第 3 行第 2 列,最多炸到 2 个僵尸。
现在请聪明的你也一起加入到消除僵尸的队伍来。
输入
第一行四个用空格隔开的正整数表示 N,M,X,Y,分别表示 N 行 M 列的地图,小星星起始位置第 X 行,第 Y 列。
接下来 N 行 M 列用来描述地图上每个单元格,‘G’表示僵尸,‘#’表示墙体,只有‘.’
表示的单元格才是小星星能够正常行走,能够放置炸弹的单元格。(数据保证四面都是墙体,也就是第 1 行、第 N 行、第 1 列、第 M 列肯定都是墙体)。
输出
一个整数,最多能炸掉的僵尸数量。
样例输入 Copy
13 13 4 2
#############
###…GG#GGG.#
###.#G#G#G#G#
#…#…G#
#G#.###.#G#G#
#GG.GGG.#.GG#
#G#.#G#.#.#.#
##G…G…#
#G#.#G###.#G#
#…G#GGG.GG#
#G#.#G#G#.#G#
#GG.GGG#G.GG#
#############
样例输出 Copy
10
提示
100%的数据,保证 N,M<2001, X<N,Y<M
蒟蒻博主这道题写了两天。大致记录一下每一种思路。
1,(TLE)先尝试用bfs从起点开始走,之后每个点都计算所能炸到的僵尸数量,用时2900+ms,严重超时。
2,(TLE&&MLE)之后采用预处理,先用两个双重循环找出所有非墙的点能炸到的僵尸数量,存到一个二维数组中(AC代码中依旧采用该预处理,但储存方式不一样),不仅多了50ms还把内存爆了。。
3,(TLE&&MLE)然后开始各种尝试缩减内存,short int 都用上了,省的内存少的可怜。
4,(MLE)bfs不行就尝试dfs,依然保留预处理方式,用dfs遍历并更新最大值,用时100+ms合格,但内存190k左右。。。
5,(AC)博主的bfs队列用的是结构体存坐标,结构体和二维数组都存坐标就造成了浪费,以至于爆内存,便采用线性表示存储坐标,也就是结构体的下标写成x*maxn+y,遍历改用灌水法,先将能到达的地方用bool vis二维数组存储节省空间 ,最后遍历一次就可以了。
AC代码:(37240KB 701MS)
#include <iostream>
#include <queue>
using namespace std;
typedef short int si;//本题最大可能出现的数为4000,用short int 完全可以
const si maxn = 2001;
si ans,n,m;
si dx[4]={
0,0,1,-1},dy[4]={
1,-1,0,0};
bool vis[maxn][maxn];
struct node
{
si x,y;//存坐标
char t;//字符
si num;//该坐标能炸到的僵尸数量
} s[maxn*(maxn+1)+1];//线性表示坐标,为方便处理,选择1到n不是0到n-1读入,这里稍微扩大
node now,nt;
queue<node>q;
void bfs(si x,si y)//广搜
{
vis[x][y]=true;
now = s[x*maxn+y];//提取对应坐标的所有信息
q.push(now);
while(!q.empty())
{
now = q.front();
for(si i = 0;i<4;i++)
{
si nx = now.x + dx[i];
si ny = now.y + dy[i];
if(nx<1||nx>n||ny<1||ny>m||s[nx*maxn+ny].t!='.'||vis[nx][ny])continue;
else
{
vis[nx][ny] = true;
q.push(s[nx*maxn+ny]);//压入下一个坐标的所有信息
}
}
q.pop();
}
}
int main()
{
si x,y,i,j,k;
cin>>n>>m>>x>>y;
for(i = 1; i<=n; i++)
for(j = 1; j<=m; j++)
{
s[i*maxn+j].x = i;//横坐标
s[i*maxn+j].y = j;//纵坐标
cin>>s[i*maxn+j].t;//坐标对应字符
}
si sum=0;//下面是预处理,分横向纵向两次
//从左往右,碰到墙就结束该次炸到的僵尸数量统计
for(i = 1;i<=n;i++)
{
for(j = 1;j<=m;j++)
{
if(s[i*maxn+j].t!='#')//
{
if(s[i*maxn+j].t=='G')
sum++;
}
else
{
for(k = j-1;k>=1&&s[i*maxn+k].t!='#';k--)//僵尸数量赋值给僵尸点已经空点(僵尸会挡路,但无妨)
{
s[i*maxn+k].num = sum;
}
sum = 0;
}
}
}
//纵向同理
for(j = 1;j<=m;j++)
{
for(i = 1;i<=n;i++)
{
if(s[i*maxn+j].t!='#')
{
if(s[i*maxn+j].t=='G')
sum++;
}
else
{
for(k = i-1;k>=1&&s[k*maxn+j].t!='#';k--)
{
s[k*maxn+j].num += sum;
}
sum = 0;
}
}
}
bfs(x,y);
for(i = 1;i<=n;i++)
{
for(j = 1;j<=m;j++)
{
if(vis[i][j]&&ans<s[i*maxn+j].num)ans=s[i*maxn+j].num;//能走到并炸到更多就更新答案
}
}
cout<<ans;
return 0;
}
线性表示是看到学长在创建项目名时采用的编号112501(11月25日第1份代码)学到的,在这道题上算是抖机灵吧,感谢支持。