可怕的宇宙射线(dfs+剪枝)

写在前面:
对于这个问题,首先我们可以采用暴力bfs或者dfs,但是这种算法的复杂度是指数级的,如果考虑分裂30次,那么它的循环次数是 2 30 2^{30} ,如果在正规比赛中肯定会超时,在询问了大佬之后,这个问题可以采用dfs加上剪枝来解决,这里和大家分享一下

问题描述:

在浩瀚的宇宙中,存在着1种生物, 这种生物可以发出宇宙射线!宇宙射线可以摧毁人的智商,进行降智打击!
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右 4 5 45^{\circ} 方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂 n n 次,每次分裂后会在分裂方向前进 a a 个单位长度。
你的任务是计算出会有多少个位置被降智打击。

input:
输入第一行包含一个正整数 n ( n 30 ) n(n\leq 30) ,表示宇宙射线会分裂 n n
第二行包含n个整数 a 1 a 2 . . . a n a_{1},a_{2}...a_{n} ,第 i i 个数 a i ( a i 5 ) a_{i}(a_{i}\leq 5) 表示第 i i 次分裂的宇宙射线会在它原方向上继续走过多少个单位长度
output:
输出一个数 a n s ans ,表示有多少个位置会被降智打击

样例输入:

4
4 2 2 3 

样例输出:

39

样例解释:
宇宙射线的分裂过程
在这里插入图片描述

解题思路:

首先我们通过观察,宇宙射线的分裂其实是对称的,我们只要求一半,另一半的坐标就可以通过对称得到。所以,在每一次分裂时,我们只考虑一个方向,另一个方向可以通过对称得到。但是,不同的边的对称次数是不同的,例如,第一条边,也就是图中的出发边,它不需要对称,最后一条边,也是图中紫色的边,它需要对称3次。由此,我们可以得到,如果宇宙射线分裂n次,那么最后一条边需要对称n-1次,倒数第二条边需要对称n-2次,由此类推,所以,我们就想到用dfs,首先遍历到最后一条边,然后从最后一条边开始遍历并回溯,回溯每一层最后一条边都会对称一次,其余边同理,这样就能满足我们的对称需求。

在每一次分裂时,都会有两个方向分裂,这里我们只考虑一个方向,朝右 4 5 45^{\circ} 方向分裂,所以这里我指定了两个偏移量数组,第0个方向是竖直向上分裂,然后按逆时针方向每变换 4 5 45^{\circ} 度为一个方向,那么每一次分裂的方向计算为,假设原方向为 d i r dir ( d i r + 7 ) m o d 8 (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;//对象中元素的个数为我们所要的答案
} 
发布了21 篇原创文章 · 获赞 4 · 访问量 899

猜你喜欢

转载自blog.csdn.net/zhL816/article/details/104878766
今日推荐