CSP M1-C 可怕的宇宙射线 dfs剪枝

题意:

宇宙射线在二维平面内传播,一段距离后向左右45°分裂,威力不变。宇宙射线会分裂n次,每次分裂后前进ai个单位再分裂。计算共有多少二维平面内的点受到宇宙射线的攻击。

Input:

第一行:分裂次数 n(n<=30)

第二行:n个数,第i个数ai表示第i次分裂后前进的距离,ai<=5

Output:

一个数ans,表示共有ans个点被攻击

题目分析:

每一个点的状态有横坐标、纵坐标、分裂次数、前进方向四个特征,使用bool型四维数组vis[x][y][k][d]记录状态,1表示经过这个状态,0表示没有经过。采用dfs递归搜索,转移方式用dx[],dy[],map<int,pair<int,int> >mp完成,每个方向用0-7的一个数字表示,dx、dy为该方向的横、纵坐标变化量,mp将一个方向映射到左右45°的两个方向。dfs的传入参数为横坐标x,纵坐标y,分裂次数cnt,前进方向dir。分裂次数为0—n-1,若cnt=n,递归结束。若(x,y,cnt,dir) 这个状态的vis为1,即在同一轮分裂的同一方向已经搜索过该点,则不必再重复搜索(剪枝),递归结束。否则,将该状态对应的vis标记为1,做m次转移(m为该轮分裂的前进步数),将每次转移后经过的点加入一个set(set可以去重)。递归搜索下一个状态,下一个状态的横、纵坐标为m次转移后的横、纵坐标,方向为将当前方向利用mp映射后的两个方向,分裂次数+1。递归结束后,set中的元素数即为结果。

注意:

—WA

  • dx,dy,mp的对应关系写错,实际上,如果按照顺时针写,十分有规律,可以避免错误
int dx[]={0,1,1, 1, 0,-1,-1,-1};// 上 右上 右 右下 下 左下 左 左上 
int dy[]={1,1,0,-1,-1,-1, 0, 1};//  0  1   2   3  4   5   6   7
map<int,pair<int,int> > mp={
{0,{7,1}} , {1,{0,2}} , {2,{1,3}} , {3,{2,4}} , 
{4,{3,5}} , {5,{4,6}} , {6,{5,7}} , {7,{6,0}}
};
  • vis数组的第三维使用"还需要前进的步数",但是,在同一个点,同样的前进方向,当还需要前进的步数相同时,只能保证这一次分裂后的位置是一样的,之后的分裂不同会导致完全不同的结果。

—MLE、TLE

题意中的次次分裂很容易联想到像波纹那样层层扩展的bfs,纯裸bfs结束时队列中有元素2^n+1个,导致MLE。即便最后一层的节点不加入队列,队列中元素最多时有2^(n-1)个,n=30时队列中最多有512×1024×1024个点,MLE。

尝试应用同样的vis四维数组去重,TLE。

bfs常用于找单一的最短路径,搜到就是最优解(迷宫、倒水)。dfs用于找问题的所有解(八皇后、宇宙射线、选数),空间效率高,找到的不一定是最优解,需要高效的剪枝。

代码:

#include<cstdio>
#include<iostream>
#include<map> 
#include<set>
#include<utility>
using namespace std;
int num[35];//第i次分裂的步数
int n;//分裂数 0-n-1  
bool vis[305][305][31][8];//坐标(i,j) 第k次分裂 目前方向为l
int dx[]={0,1,1, 1, 0,-1,-1,-1};// 上 右上 右 右下 下 左下 左 左上 
int dy[]={1,1,0,-1,-1,-1, 0, 1};//  0  1    2   3   4   5   6   7
int sx=150,sy=149;//起始点的前驱 
map<int,pair<int,int> > mp={
	{0,{7,1}} , {1,{0,2}} , {2,{1,3}} , {3,{2,4}} , {4,{3,5}} , {5,{4,6}} , {6,{5,7}} , {7,{6,0}}
};
struct point{
	int x;int y; 
	point(int a,int b){
		x=a;y=b; 
	} 
	bool operator<(const point& p)const
	{
		return x==p.x ? y<p.y : x<p.x;
	}
};
set<point> s;
void dfs(int x,int y,int cnt,int dir)//坐标(x,y) 将要执行第cnt次分裂  目前的方向为dir 
{
	if(cnt==n) return;
	if(vis[x][y][cnt][dir]==1) return ; 
	int tx=x,ty=y;

	vis[x][y][cnt][dir]=1;
	for(int i=0;i<num[cnt];i++)
	{
		tx+=dx[dir]; ty+=dy[dir];
		s.insert(point(tx,ty));
	} 
	dfs(tx,ty,cnt+1,mp[dir].first);
	dfs(tx,ty,cnt+1,mp[dir].second);
} 
int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&num[i]);
	dfs(sx,sy,0,0);
	printf("%d\n",s.size());
}

规模与限制:

数据点  n
10%    <=10
40%    <=20
100%   <=30
每个测试点1000ms 256MB

 

样例输入:

4
4 2 2 3

样例输出:

39

样例解释: 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值