题意:
众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂 次,每次分裂后会在分裂方向前进ai个单位长度。现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生zjm那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"。
时间与内存限制 1000ms 2632144KB
输入输出说明:
输入第一行包含一个正整数 n(n<=30),表示宇宙射线会分裂n 次
第二行包含n个正整数a1,a2···an ,第ai (ai<=5)个数表示第 i次分裂的宇宙射线会在它原方向上继续走多少个单位长度
数据点 n
10% <=10
40% <=20
100% <=30
样例输入:
4
4 2 2 3
样例输出:
39
样例说明
思路:
算法确定:
这个题目要求计算出共有多少个位置会被"降智打击",与坐标有关,让我想起了之前讲过的“迷宫问题”,到这里我觉得可能使用bfs或dfs。在这个宇宙射线的分裂过程中,它的两边是对称的,除了方向不同之外,其余都是一样的,所以我先考虑分裂的一半,分裂结束之后,回溯到上一个分裂点,加上分裂的另一半,考虑到这,基本上可以确定使用dfs遍历了。
具体实现:
如何确定经过的点的个数?参照dfs遍历中使用vis数组进行标记,这里需要注意的是,之前做的题都是向4个方向移动,而这个题是45°角分裂,也就是有8个方向,我们还是使用常量数组dx,dy来控制移动。
上面我们已经分析过了,每次都先考虑分裂的一半,需要注意的一点是,我们跟踪哪一半的分裂就一直跟踪这一半,我在这道题中先考虑每次分裂后的右枝,所以dx,dy的定义如下:
int dx[8]={0,1,1,1,0,-1,-1,-1};
int dy[8]={1,1,0,-1,-1,-1,0,1};
剪枝:
一个点若在之前已经进行过某个方向的某种分裂,那么它之后经过的路径与上一次经过这个分裂点时的历经是一致的,也就是说它必定已经被标记过,若继续进行下去就是在做无用功,所以这里有了优化条件:设置数组divide记录是否进行过相同的分裂,若进行过,则停止遍历。
总结:
1.由分裂的对称性想到先考虑分裂的一枝
2.暴力的使用bfs,dfs遍历一般都复杂性较高,剪枝对于降低复杂性十分必要
3.八个方向合理使用dx,dy数组,保证每次跟踪的每一枝是固定的(左枝or右枝)
4.再就是注意细节!卡了好长时间的一个错误竟然是少写了逗号
代码:
#include<iostream>
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}; //方向为右45°,顺时针
int vis[301][301]; //记录该点是否到达过
int divide[301][301][31][8]; //记录该点是否分裂过
int n,res;
int a[31];
void dfs(int x,int y,int count,int direction){
if(count>n||divide[x][y][count][direction]==1) //输入结束或该点分裂过
return;
divide[x][y][count][direction]=1; //标记分裂过
int X,Y;
for(int i=1;i<=a[count];i++){
X=x+dx[direction]*i;Y=y+dy[direction]*i;
if(vis[X][Y]==0){
vis[X][Y]=1; res++; //标记到达过
}
}
count++;
int Direction=(direction+1)%8;
dfs(X,Y,count,Direction); //右枝
Direction=(direction+7)%8;
dfs(X,Y,count,Direction); //左枝
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
dfs(150,150,1,0);
cout<<res<<endl;
return 0;
}