C-可怕的宇宙射线
题目描述
众所周知,瑞神已经达到了CS本科生的天花板,但殊不知天外有天,人外有苟。在浩瀚的宇宙中,存在着一种叫做苟狗的生物,这种生物天生就能达到人类研究生的知识水平,并且天生擅长CSP,甚至有全国第一的水平!但最可怕的是,它可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n次,每次分裂后会在分裂方向前进ai个单位长度。
现在瑞神要带着他的小弟们挑战苟狗,但是瑞神不想让自己的智商降到普通本科生zjm那么菜的水平,所以瑞神来请求你帮他计算出共有多少个位置会被"降智打击"
Input:输入的第一行包含一个正整数n(n <= 30),表示宇宙射线会分裂n次。
第二行包含n个正整数a1,a2…an,ai<=5,第ai个数表示第i次分裂宇宙射线会在它原方向上继续走多少个单位长度。
Output:输出一个数,有多少个位置会被降智打击。
Exmaple:
Input:
4
4 2 2 3
Output:
39
Tips:
数据点 n
10% <=10
40% <=20
100% <=30
题目思路
根据题意可以知道,这道题是可以使用dfs或者bfs做的,为了减少运行时间,我们还可以在搜索的时候加上记忆化剪枝,降低时间复杂度。
在刚开始拿到这个题目的时候可以说是极度崩溃,除了知道要用bfs或者dfs之外其他的都是一脸懵。可是在之后和大佬讨论和思考过后慢慢有了点思路,其中也是借鉴了一下其他同学的方法。在我做这道题的时候遇到的第一个困难就是怎么来表示分裂方向,开始想到的是使用两个长度为16的数组dx,dy来表示,可是想到其中有一条边会重复不太好表示,并且在计算方向的时候也不是很方便。经过思考之后想到八边形是一个对称的图形,这样我们就只需要使用一侧的方向就可以表示。在解决这个问题之后的第二个困难就是怎么去表示各个分裂点的状态,主要有坐标,分裂层次,分裂方向。这里最开始想到的可能会是定义结构体变量来表示各个分裂点的状态,emmm…,没错,我最开始也是这么想的,其实这么做没有什么不可以的,只是说代码在写起来的时候可能会稍微麻烦一点。但是,我们还有另外一种方法,就是使用一个四维数组来表示分裂点的状态,两维表示坐标,一维表示分裂层次,一维表示分裂方向,数组的值来表示是否已经分裂。另外还需要一个数组来纪录点是否到达。
在做完这些之后我们还需要做的时分析数据。根据分裂次数n<=30,ai<=5,可以知道分裂的最远距离不会超过150,这样我们就可以将分裂的过程模拟在一个300300的坐标中,并且起始的分裂点在中心的位置即(150,150)。
经过上面的分析过后,dfs搜索的过程就是先判断起始点是否分裂,如果没有分裂,则开始分裂,分裂的过程中记录到达的点的个数,一次分裂结束后开始向左侧和右侧分裂。在分裂的国臣过程中如果遇到某个分裂点已经分裂过了或者分裂次数达到n的时候就回溯。最后的ans*就是结果。
代码实现
#include <bits/stdc++.h>
using namespace std;
#define _for(i,a,b) for(int i = (a); i < (b); i++)
#define _rep(i,a,b) for(int i = (a); i <= (b); i++)
int n , ans;
int a[40]; //分裂后的单位长度
int dx[] = {0,1,1,1,0,-1,-1,-1}; //往右侧分裂的左边变化
int dy[] = {1,1,0,-1,-1,-1,0,1}; //左侧的也可以有此推出
bool vis[310][310][30][8] = {false}; //标记点是否分裂
bool arr[310][310] = {false}; //标记点是否到达
void dfs(int x,int y,int num,int dir) //x,y为当前坐标,num记录分裂的次数,dir表示当前分裂的方向
{
if(vis[x][y][num][dir]) return; //表示该点已经分裂,回溯
vis[x][y][num][dir] = true; //没有分裂的话开始分裂
_rep(i,1,a[num]) //从该分裂点延申
{//分别向x,y方向延申
x += dx[dir];
y += dy[dir];
if(!arr[x][y]) //该点未到达
{
ans++;
arr[x][y] = true;
}
}
if(num >= n) return; //到最后一次分裂的时候回溯
//此时的坐标是需要分裂的点
//接着向右侧和左侧分裂
int newdir = (dir+1) % 8;
dfs(x,y,num+1,newdir);
//向左侧分裂
newdir = (dir+7) % 8;
dfs(x,y,num+1,newdir);
}
int main()
{
cin >> n;
_rep(i,1,n)
cin >> a[i];
dfs(150,150,1,0); //从中心点坐标开始
cout << ans << endl;
}
总结
1.在这次实验中我使用了一些之前没有使用的一些简单操作,比如万能头文件*<bits/stdc++.h>*,for循环的宏定义,大部分变量都是定义全局变量。
2.学会对数据的分析,即分析数据的边界,这样可以避免很多数据带来的不必要的麻烦。