题目:
宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n次,每次分裂后会在分裂方向前进ai个单位长度。
求宇宙射线会覆盖多少区域?
输入:
第一行包含一个正整数n(n <= 30),示宇宙射线会分裂n次
第二行包含n个正整数a1, a2…an.第i个数ai(ai <= 5)表示第i次分裂的宇宙射线会在它原方向上继续走多少个单位长度。
输出:
输出个数ans,表示有多少个位置会被覆盖。
样例输入:
4
4 2 2 3
样例输出:
39
对于这个问题,最关键的应该是要考虑到分裂的射线会在一些格子上重叠。在比赛的时候想到了要用递归,但是在递归的过程中如何处理各条分裂的边一直没什么头绪,而且如何计算所有被覆盖的点数也让我很头疼。赛后看了大佬的代码才知道要怎么做。
用set可以很好的解决重复点和点数的问题。
首先,我们不考虑其他分裂出的边,只考虑一条一直向右边转45°的边,在转了n次后,走到最后的点后,一边回溯,一边把当前的点插入set中,在到达转折点的时候,遍历一遍set,根据该转折的方向和转折点的坐标可以求出直线方程,可以利用高中的知识,求出set中的点关于这条直线的对称点,将对称点插入set,然后继续回溯,重复操作,直到最后。
关键函数solve
void solve(int dir,int k,pair<int,int> here)
{
if(k>=n)
return;
pair<int,int> p=here;//用p保存转折点
//利用偏移量方便地进行转折
offset[0].first=0;offset[0].second=1;//右转部分
offset[1].first=1;offset[1].second=1;
offset[2].first=1;offset[2].second=0;
offset[3].first=1;offset[3].second=-1;
offset[4].first=0;offset[4].second=-1;
offset[5].first=-1;offset[5].second=-1;
offset[6].first=-1;offset[6].second=0;
offset[7].first=-1;offset[7].second=1;
for(int i=0;i<a[k];i++)//在指定方向上移动a[k]步
{
here.first=here.first+offset[dir].first;
here.second=here.second+offset[dir].second;
}
solve((dir+1)%8,k+1,here);//递归
//回溯的同时将点插入set
for(int i=0;i<a[k];i++)
{
s.insert(here);
here.first-=offset[dir].first;
here.second-=offset[dir].second;
}
if(k!=0)//k=0的情况下就是最开始向上的那条边,不需要对称
for(set<pair<int,int> >::iterator it=s.begin();it!=s.end();it++)
{ //将点对称
pair<int,int> pos;
if(dir==0||dir==4)
{
pos.first=p.first+p.second-it->second;
pos.second=p.first+p.second-it->first;
}
else if(dir==1||dir==5)
{
pos.first=2*p.first-it->first;
pos.second=it->second;
}
else if(dir==2||dir==6)
{
pos.first=it->second-p.second+p.first;
pos.second=it->first+p.second-p.first;
}
else if(dir==3||dir==7)
{
pos.first=it->first;
pos.second=2*p.second-it->second;
}
s.insert(pos);
}
}
以下是全部代码:
#include<iostream>
#include<set>
using namespace std;
int a[100];
int n;
pair<int,int> offset[8];
set<pair<int,int> > s;
void solve(int dir,int k,pair<int,int> here)
{
if(k>=n)
return;
pair<int,int> p=here;
//利用偏移量方便地进行转折
offset[0].first=0;offset[0].second=1;//右转部分
offset[1].first=1;offset[1].second=1;
offset[2].first=1;offset[2].second=0;
offset[3].first=1;offset[3].second=-1;
offset[4].first=0;offset[4].second=-1;
offset[5].first=-1;offset[5].second=-1;
offset[6].first=-1;offset[6].second=0;
offset[7].first=-1;offset[7].second=1;
for(int i=0;i<a[k];i++)//在指定方向上移动a[k]步
{
here.first=here.first+offset[dir].first;
here.second=here.second+offset[dir].second;
}
solve((dir+1)%8,k+1,here);//递归
//回溯的同时将点插入set
for(int i=0;i<a[k];i++)
{
s.insert(here);
here.first-=offset[dir].first;
here.second-=offset[dir].second;
}
if(k!=0)
for(set<pair<int,int> >::iterator it=s.begin();it!=s.end();it++)
{//将点对称
pair<int,int> pos;
if(dir==0||dir==4)
{
pos.first=p.first+p.second-it->second;
pos.second=p.first+p.second-it->first;
}
else if(dir==1||dir==5)
{
pos.first=2*p.first-it->first;
pos.second=it->second;
}
else if(dir==2||dir==6)
{
pos.first=it->second-p.second+p.first;
pos.second=it->first+p.second-p.first;
}
else if(dir==3||dir==7)
{
pos.first=it->first;
pos.second=2*p.second-it->second;
}
s.insert(pos);
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
pair<int,int> here;
here.first=0;
here.second=0;
solve(0,0,here);
cout<<s.size()<<endl;
}