题意:
宇宙射线在二维平面内传播,一段距离后向左右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
样例解释: