C-可怕的宇宙射线(dfs记忆剪枝)

C-可怕的宇宙射线

一、题目描述

时间与内存限制 1000ms 2632144KB

众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂 次,每次分裂后会在分裂方向前进ai个单位长度。现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生zjm那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"。

输入说明

输入第一行包含一个正整数 n(n<=30),表示宇宙射线会分裂n 次
第二行包含n个正整数a1,a2···an ,第ai (ai<=5)个数表示第 i次分裂的宇宙射线会在它原方向上继续走多少个单位长度

输出说明

输出一个数ans ,表示有多少个位置会被降智打击

样例输入

4
4 2 2 3

样例输出

39

数据点说明

数据点    n
10%     <=10
40%     <=20
100%    <=30

样例解释

二、思路与算法

这道题目前看比较难,因为要处理很多环节,再把它们组合在一起形成答案。

  • 如何存储所有的点?
    答:用int数组存储,根据给出的数据范围得知,最多分裂30次,每次最多扩散5个单位,所以每次就算一直向上走,也不会超过150个单位。
    所以,为了用数组存储,要让所有位置x,y值都为非负数,则起始点设为(150,150),整个数组横纵坐标为300*300。(比这个大小更大也完全可以)

  • 如何标记一个点是否来过?(去重)
    答:用二维数组vis记录,每到一个点,检查是否到达过,未到达过则打上标记。

  • 如何把要分裂的点和其他点区分开?
    答:用另外一个四维数组a记录所有要分裂的位置。

  • 如何确定下一次分裂的方向?
    答:也在四维数组a中记录,用一个int型数据标记。
    这里涉及到找分裂方向的规律的问题。
    dfs中总会有一个趋向,在有多种选择时,先遍历哪个方向,这个趋向可以随便选,选出来就可以。
    这里我们选择趋向右侧。
    向上——>右上——>向右——>右下——>向下——>左下——>向左——>左上——>向上
    画出图来就是一个正八边形。
    这个8个方向构成循环,是我们确定分裂方向的手段。
    后面对左侧分裂进行讨论时,也是这样一个正八边形,只是起点不同,终点不同,前后相接的次序维持不变。
    一般,我们dfs时都有设2个数组:dx、dy,用来遍历某个位置所有相邻位置。
    那么,这里我们有8个相邻元素,并且注意!因为分裂方向有规律有序改变,所以dx、dy的内容也要符合这种变化顺序!
    每次我们用四维数组里记录的int型与dx、dy顺序结合起来判断。

  • 如何剪枝才能使时间复杂度尽可能地小?
    答:记忆性剪枝!某个点已经分裂过,则不要再从这个点分裂了。因为如果现在的情况以前遇到过,那么再继续下去也和以前的经历完全一样。

  • 如何记忆性剪枝而不遗漏点?
    答:if判断+四维数组记录是否从这个位置分裂过。

三、代码实现

要重点理解怎么剪枝、怎么遍历、怎么统计所有点而不重复!

#include<cstdio>
using namespace std;

int dx[8]={0,1,1,1,0,-1,-1,-1};
int dy[8]={1,1,0,-1,-1,-1,0,1};//优先右侧的分裂,是一个八边形循环,dx、dy顺序严格! 
int result=0;
int n=0;
int a[300][300][30][8]={0};//标记某个顶点是否分裂过 
int vis[300][300]={0};//标记某点是否到达过 
int st[30]={0};

void dfs(int x,int y,int num,int dire){
	if(num==n){return;}  //分裂次数足够了
	if(a[x][y][num][dire]==0){//没从这个点分裂过 记忆剪枝,若某个点分裂过,则不再从它分裂
		a[x][y][num][dire]=1;
		//从这个点分裂  
		for(int j=1;j<=st[num];j++){
			if(vis[x+dx[dire]*j][y+dy[dire]*j]==0){
				vis[x+dx[dire]*j][y+dy[dire]*j]=1;	result++;
			}
		}
		//继续下一次分裂
		int nextX=x+dx[dire]*st[num];
		int nextY=y+dy[dire]*st[num];
		int nextDire=(dire+1)%8;
		dfs(nextX,nextY,num+1,nextDire);//下一点右侧方向分裂
		
		nextDire=(dire+7)%8;
		dfs(nextX,nextY,num+1,nextDire);//下一点左侧方向分裂 
	}
}


int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%d",&st[i]);
	}
	dfs(150,150,0,0);   
	printf("%d",result);
	return 0;
}

四、经验与总结

  1. 记忆性剪枝真的是dfs、bfs等降低时间复杂性的利器,影响非常大。
  2. 整个题难度较大,不仅考察了dfs和记忆性剪枝,还考察了很多思维上的问题。比如说,我一开始想的是构造结构体,在结构体里记录各种数据(甚至包括x、y分别的正负号),申请结构体二维数组,但明显这样写的代码长度、繁琐度都远超过这个写法。可以尝试用更简练的代码实现同样的功能!
  3. bfs算法猜想:一层一层地遍历,每遍历一层,去重、记忆性剪枝。(总体上和dfs差不多)
  4. 题目很多细节小点也很值得注意,比如原点定在(150,150)、分裂方向的规律(用dxdy而非一堆if)等。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值