写在前面:
对于这个问题,首先我们可以采用暴力bfs或者dfs,但是这种算法的复杂度是指数级的,如果考虑分裂30次,那么它的循环次数是 2 30 2^{30} 230,如果在正规比赛中肯定会超时,在询问了大佬之后,这个问题可以采用dfs加上剪枝来解决,这里和大家分享一下
问题描述:
在浩瀚的宇宙中,存在着1种生物, 这种生物可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右
4
5
∘
45^{\circ}
45∘方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂
n
n
n次,每次分裂后会在分裂方向前进
a
a
a个单位长度。
你的任务是计算出会有多少个位置被降智打击。
input:
输入第一行包含一个正整数 n ( n ≤ 30 ) n(n\leq 30) n(n≤30),表示宇宙射线会分裂 n n n次
第二行包含n个整数 a 1 , a 2 . . . a n a_{1},a_{2}...a_{n} a1,a2...an,第 i i i个数 a i ( a i ≤ 5 ) a_{i}(a_{i}\leq 5) ai(ai≤5)表示第 i i i次分裂的宇宙射线会在它原方向上继续走过多少个单位长度
output:
输出一个数 a n s ans ans,表示有多少个位置会被降智打击
样例输入:
4
4 2 2 3
样例输出:
39
样例解释:
宇宙射线的分裂过程
解题思路:
首先我们通过观察,宇宙射线的分裂其实是对称的,我们只要求一半,另一半的坐标就可以通过对称得到。所以,在每一次分裂时,我们只考虑一个方向,另一个方向可以通过对称得到。但是,不同的边的对称次数是不同的,例如,第一条边,也就是图中的出发边,它不需要对称,最后一条边,也是图中紫色的边,它需要对称3次。由此,我们可以得到,如果宇宙射线分裂n次,那么最后一条边需要对称n-1次,倒数第二条边需要对称n-2次,由此类推,所以,我们就想到用dfs,首先遍历到最后一条边,然后从最后一条边开始遍历并回溯,回溯每一层最后一条边都会对称一次,其余边同理,这样就能满足我们的对称需求。
在每一次分裂时,都会有两个方向分裂,这里我们只考虑一个方向,朝右 4 5 ∘ 45^{\circ} 45∘方向分裂,所以这里我指定了两个偏移量数组,第0个方向是竖直向上分裂,然后按逆时针方向每变换 4 5 ∘ 45^{\circ} 45∘度为一个方向,那么每一次分裂的方向计算为,假设原方向为 d i r dir dir: ( d i r + 7 ) m o d 8 (dir+7)mod8 (dir+7)mod8,这样我们就得到了每一次分裂的方向。每个射线都是沿分裂方向发散n个长度。
在考虑完方向问题,还有一个对称问题,对称线一共有四条,如下图:
PS:
几幅图旨在说明对称关系,和实际的分裂图的情况有些不类似,并且对称点的计算方法高中都学过,这里直接给出了对称点的坐标。
有了这几个对称关系,我们就能很容易的找到已经遍历的对称点。
对于算法实现,上述都已经描述的差不多了。这里我使用了stl中的set,因为他有一个好处是自动去重,这样我们就不用考虑一个点是否已经到达过,直接将所有的点插入set中,set自动去除重复的点。这样最后set中元素的个数就是我们的结果。
解题总结:
对于这个题目,我们最直接想到的就是暴力bfs和dfs,如果在没有时间约束的条件下这样也是可行的,如果一旦加上了时间约束,这往往就不尽人意了,所以我们要在此基础上加上合理的剪枝,进一步减少冗余的工作,减少时间。
代码:
#include<iostream>
#include<queue>
#include <string.h>
#include<cmath>
#include<set>
using namespace std;
struct point{ //点结构体
int x;
int y;
bool operator < (const point p)const{ //重载小于号,便于set使用
if(x!=p.x) return x<p.x;
return y<p.y;
}
};
set<point> path; //全局变量,存储已经到的点
int a[31];
//八个方向,第0个方向为默认的向上方向
int x_move[]={ 0,1,1,1,0,-1,-1,-1 };
int y_move[]={ 1,1,0,-1,-1,-1,0,1 };
//startx和starty是分裂时的点
void search(int pnum,int dir,int startx,int starty,int n)
{//找到可以到达的点
if(pnum>=n) return; //递归结束
int move_n=a[pnum];
int temp_x=startx;
int temp_y=starty;
//递归到最后一个点,从最后一条边开始回溯,不断对称
search(pnum+1,(dir+7)%8,startx+move_n*x_move[dir],starty+move_n*y_move[dir],n);
for(set<point>::iterator i=path.begin();i!=path.end();i++)
{//对已经到的点进行对称,共四个方向
if(dir==0||dir==4)
{
point temp;
temp.x=2*startx-i->x;
temp.y=i->y;
path.insert(temp);
}
else if(dir==1||dir==5)
{
point temp;
temp.x=i->y+startx-starty;
temp.y=i->x+starty-startx;
path.insert(temp);
}
else if(dir==2||dir==6)
{
point temp;
temp.x=i->x;
temp.y=2*starty-i->y;
path.insert(temp);
}
else if(dir==3||dir==7)
{
point temp;
temp.x=startx+starty-i->y;
temp.y=startx+starty-i->x;
path.insert(temp);
}
}
//将已经到的边对称完,然后遍历以startx和starty为终点的边
for(int i=0;i<move_n;i++)
{
point temp;
temp.x=startx+x_move[dir];
temp.y=starty+y_move[dir];
startx=temp.x;
starty=temp.y;
path.insert(temp);
}
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
search(0,0,0,0,n);
cout<<path.size()<<endl;//对象中元素的个数为我们所要的答案
}