题目大意
本题给出了n个整数的数组a[n],题目表示有一条初始方向为上的宇宙射线,先走过a[0]长度,之后会向其行走方向左右45°进行分裂,分裂后行走a[1]的长度,开始与上述相同的分裂。因此,题目中给出的n即为分裂次数减一,a[i]表示第i次分裂后要走的长度。题目最后需要求出分裂结束时一共覆盖了多少个点。分裂示意图如下:
解题思路
本题乍一看可以暴力遍历求解,但遍历的时间复杂度会达到O(2ⁿ),因此会超时。于是分析可知本题可以通过记忆化搜索的方式来完成,鉴于本题这种情况有很强的对称性,且为了剪枝去重,于是采用dfs来实现。
dfs搜索时,将二维坐标表示为二维数组,由于总长度小于等于30×5=150,所以可以使用300×300的二维数组来表示。为了保证所有点坐标都被记录为正数,起点可以选为[150][150]。随后开始搜索,用一个四维数组来进行剪枝记录,四个维度分别为x坐标、y坐标、已分裂次数、当前朝向。分析可知当上述四组数据都相同时,可以理解为对称情况,直接剪枝即可。每当搜索到一个新的点,将ans加一,同时用一个vis[x][y]数组来标记当前点,防止重复计数。
本题需要特别注意的即为起点坐标和剪枝方法,若是数组过大或是不采用记忆化搜索,均会出现相应的错误。
具体代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<iomanip>
#include<vector>
#include<queue>
#define ll long long
#define ld long double
using namespace std;
int dx[8] = {0,1,1,1,0,-1,-1,-1};
int dy[8] = {1,1,0,-1,-1,-1,0,1};
int ans = 0,n;
int flag[300][300][30][8];
int vis[300][300];
int a[30];
void dfs(int x, int y, int to, int cn)
{
if(cn == n)
{
return;
}
if(flag[x][y][cn][to])
{
return;
}
flag[x][y][cn][to] = 1;
for(int i = 1; i <= a[cn]; i++)
{
if(!vis[x+dx[to]*i][y+dy[to]*i])
{
ans++;
vis[x+dx[to]*i][y+dy[to]*i] = 1;
}
}
dfs(x+dx[to]*a[cn],y+dy[to]*a[cn],(to+1)%8,cn+1);
dfs(x+dx[to]*a[cn],y+dy[to]*a[cn],(to+7)%8,cn+1);
}
int main()
{
int temp,tot = 0;
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
dfs(150,150,0,0);
cout << ans;
return 0;
}