Week4 CSP-M1 C - 可怕的宇宙射线
题目描述:
宇宙射线可以摧毁人的智商,进行降智打击!宇宙射线会在无限的二维平面上传播(可以看做一个二维网格图),初始方向默认向上。宇宙射线会在发射出一段距离后分裂,向该方向的左右45°方向分裂出两条宇宙射线,同时威力不变!宇宙射线会分裂n次,每次分裂后会在分裂方向前进ai个单位长度。计算共有多少个位置会被打击。
下面这个图,从上往下看
数据范围:
思维履历:
起初的思路很混乱,但是无外乎两个方向:一个是对称;一个是试着找公式,公式我觉得我如果能找到,还得修炼个几年
说说对称:之所以没写对称是因为遇到了如下几个问题(当时想着用数组存储这个图,有填充是1,没填充是0):
① 填充完一条线后,为了对称找新的点,怎么找出现有的已经被填充的所有点?很显然遍历数组不行
② 为了解决①,很显然,我可以不用模拟,而用点的结构体数组只存储当前已经被填充的点,然后坐标变换,得到新的点再加进来,但是出现了新的问题,就是在这个新的结构体数组中,有很多重复的点,怎么去重?为了解决这个问题,我发现了集合。
然后,集合真是个好东西,直接把点存集合里就完了
AC代码的基本思路:
第一次不算分裂只算前进,最后特殊处理;
从第二次分裂开始,每层递归都是如下过程:蔓延(分裂)出一条线,填充这条线(把这条线上的点放入集合),进入下一层(以当前这条线的末尾坐标为下一层的分裂点),找出集合的所有点,产生对称点,然后加入集合;
递归结束后,再加上第一次分裂的那条线,完毕。
代码:
① 纯DFS,40分,n>20时 TLE
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //暴力搜索 2 #include <cstdio> 3 #include <iostream> 4 #include <cstring> 5 #include <algorithm> 6 using namespace std; 7 bool m[5005][5005]; 8 int a[40]; 9 int n,ans=0; 10 //右,右下,下,左下,左,左上,上,右上 11 //最初箭头指向dir=6,向上走 12 int dx[]={0,+1,+1,+1,0,-1,-1,-1}; 13 int dy[]={+1,+1,0,-1,-1,-1,0,+1}; 14 void dfs(int dir,int num,int x,int y) //从dir方向分裂,该分裂是第num次分裂,走a[num]步,分裂的点是x,y 15 { 16 if(num>n) return; 17 //分裂的两个方向 18 int l=(dir-1+8)%8; 19 int r=(dir+1)%8; 20 int new_x=x,new_y=y; 21 //向左分裂 22 for(int i=1;i<=a[num];i++) 23 { 24 new_x+=dx[l],new_y+=dy[l]; 25 m[new_x][new_y]=1; 26 } 27 dfs(l,num+1,new_x,new_y); 28 29 //向右分裂 30 new_x=x,new_y=y; 31 for(int i=1;i<=a[num];i++) 32 { 33 new_x+=dx[r],new_y+=dy[r]; 34 m[new_x][new_y]=1; 35 } 36 dfs(r,num+1,new_x,new_y); 37 } 38 int main() 39 { 40 memset(m,0,sizeof(m)); 41 cin>>n; 42 for(int i=1;i<=n;i++) 43 scanf("%d",a+i); 44 int x=2500; 45 for(int i=1;i<=a[1];i++) 46 m[x--][2500]=1; 47 48 dfs(6,2,2500-a[1],2500); 49 50 for(int i=0;i<5005;i++) 51 for(int j=0;j<5005;j++) 52 if(m[i][j]==1) ans++; 53 54 cout<<ans<<endl; 55 return 0; 56 }
② 用对称和集合改进的代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /* 用set的好处:最后不必遍历整个数组; 可以存负数的边 ;*/ 2 #include <cstdio> 3 #include <iostream> 4 #include <cstring> 5 #include <algorithm> 6 #include <set> 7 using namespace std; 8 struct point 9 { 10 int x,y; 11 bool operator< (const point &X) const 12 { 13 if(x!=X.x) return x<X.x; 14 else return y<X.y; 15 } 16 }; 17 set <point> S; 18 int a[40]; 19 int n,ans=0; 20 //右,右下,下,左下,左,左上,上,右上 21 //最初dir=6,向上走 22 int dx[]={ +1, +1, 0, -1, -1, -1, 0, +1}; 23 int dy[]={ 0, -1, -1, -1, 0, +1, +1, +1}; 24 void dfs(int dir,int num,int x,int y) //从dir方向分裂,该分裂是第num次分裂,走a[num]步,分裂的点是x,y 25 { 26 if(num>n) return; 27 //分裂的方向 28 int r=(dir+1)%8; 29 30 //new_x和new_y是下一次分裂并且位移后 31 int new_x=x+dx[r]*a[num] , new_y=y+dy[r]*a[num]; 32 dfs(r,num+1,new_x,new_y); 33 34 //填充这一层 35 new_x=x,new_y=y; 36 for(int i=1;i<=a[num];i++) 37 { 38 new_x+=dx[r],new_y+=dy[r]; 39 S.insert( {new_x,new_y} ); 40 } 41 42 set<point> St; 43 for(auto t:S) //遍历集合中的每一个点,生成对称点 44 { 45 //判断这一层是通过哪个方向的分裂进入到下一层的 46 switch(r%4) 47 { 48 case 1: St.insert( { t.x,y-t.y+y } ); break; //关于y=y对称 49 50 case 2: St.insert( { x+y-t.y,x+y-t.x } ); break; //关于y=-x 对称 没问题 51 52 case 3: St.insert( { x+(x-t.x),t.y } ); break; //关于x=x对称 53 54 case 0: St.insert( { x+t.y-y,y+t.x-x } ); break; //关于y=x对称 55 } 56 } 57 S.insert( St.begin(),St.end() ); 58 59 } 60 int main() 61 { 62 // freopen("a.in","r",stdin); 63 cin>>n; 64 for(int i=1;i<=n;i++) 65 scanf("%d",a+i); 66 67 dfs(6,2,5000,5000); 68 for(int i=0;i<a[1];i++) 69 { 70 S.insert( {5000,5000-i} ); 71 } 72 cout<<S.size()<<endl; //有可能起点也被填充了多次,所以 73 return 0; 74 }
总结:
比赛时也要冷静分析,问题抽象
需多多了解STL,能省很多事
两个问题:
1、对称直线的斜率是1,但方程不一定是y=x,因为不一定经过原点
2、对拍时,如果只拍了一组数据(输出相同)就停了,可能是代码中有 system("pause")等类似语句使程序不能直接结束