当前位置:   article > 正文

2024年第十五届蓝桥杯C/C++大学B组题 C传送阵_蓝桥杯2024年第十五届省赛真题-传送阵

蓝桥杯2024年第十五届省赛真题-传送阵

前言:本道题目难度一般,当时博主的主要思路和复盘的时候差不多,但是比赛的时候漏了某些特殊情况,导致本题 “ 全盘崩溃 ”。复盘的是也是发现了这些问题,又因为是秋后算账,怎么来都无所谓,所以博主在本题的解法上加了些自己的 “ 私活 ”  (为了让题目更直观),接下来是本道题的解析。

一、题目分析

       提取题目的关键信息: n 个传送阵,进门会从 i 门传送到 a[ i ] 门,走到某个循环门点 j 时可以使用魔法,传送到 j 门的前一扇或者后一扇,重复经过的门不计入次数,求最多可能进入不同传送门的次数。

       首先从传送阵出发,n个数字随机排序没有重复,并且按照题目要求是由检方输入生成的,那么我们就给 n 个传送阵创造好一个数组去存放这样一个排列顺序,循环n次,每次用scanf输入到数组a [ ]中,接下来这个数组 a[ ] 在本题里将作为“常量”处理,也就是a数组的内容不会发生任何改变,它要用来做对照组。

       其次,从传送门的计次看,我们以小蓝进到某一扇门的时间为节点,对其经过的门数计次,也就是说当小蓝从i门出发时,i 门已经算入到总次数里,总次数 nums 开始自增。

       再看传送的过程,传送过程包括起始、路径、重复判断等问题。根据题目要求,小蓝要尽可能经过所有的传送门,其出发点是任意的,这里就需要添加一个循环给a数组遍历一遍起始位置,从1开始到n开始都要轮一遍。路径上 i 门的下一个位置是 a [ i ] ,但是,每进一扇门我们都需要对其进行重复判断,所以我们还需要一个数组 b [ ] 去判断是否重复,这里我们采用累加法,即 b [ i ] = 0为没有重复 b [ i ] >=1为重复一旦重复,我们则需要使用魔法,这里设置一个变量magic来判断是否使用了魔法,初值magic=1,使用魔法后归零(magic=0)魔法可以传送两个方向,如果标记使用魔法的位置再对其编入两个分支也是可以的,不过博主在这里比较推荐再创建一个判断数组   c [ ] 来判断,其功能和b数组是一致的,这样就区分开来,b数组作为使用魔法时向右传送的一组,c数组则是使用魔法时向左传送的一组。因为是一轮遍历时的两种状况,所以两者都需要从首位置开始判断,并且两种取向可能会导致最终的结果不同,我们还需要取其中次数的较大值

       接下来讲讲重头戏——重复的判断。在本道题里,重复的可能性只有2种,一种是自我封闭式循环,例如 2号门的下一个传送点是还是2号,这个时候如果不用魔法是走不出去的,同理,如果使用魔法传送到这样的位置也是出不来的,一旦进入这种类型的传送门,在魔法耗尽的情况下就应该立即结束循环。还有另一种则是头尾相连的循环,即小蓝从某一门进入,最后会回到该门,这是本题最多也是最普遍出现的情况,只要b数组对已经走过的位置进行了累加标记,那么我们只需要判断其下一个是否重复就可以了,一旦重复只能使用魔法。

        这样一看逻辑就非常清晰了,但是距离代码的实现还缺了一点东西。由于我们采用了 b [   ] 和 c [  ] 两个数组作为判断,在一遍遍历后,里面的值会发生改变,下一次使用时,因为没有清零,一定会导致程序出错,所以在每一次遍历完成后,我们要对数组进行清零操作。一共有两种方法,一种是用memset函数,一种是用fill函数,博主这里采用的是后者,用memset会出错(没有赋初值)fill函数的用法非常简单,fill(数组名,数组起始位置+往后n个位置,赋予的值)

输入

  1. int a[5];//一个含有5个元素的int数组
  2. fill(a,a+5,0);//a为数组名,也是数组起始位置,注意第二项需要赋值几个就写几个,最多数量和元素个数一致
  3. for(int i = 0;i<5;i++)//输出a数组的值
  4. cout << ' ' << a[i];

对a数组输出

 0 0 0 0 0

清零也完成后就可以开始写我们的代码了,还有需要注意的就是我们把数组0号位空出来,写代码的时候思路会清晰一点

二、代码实现

  1. #include<bits/stdc++.h>//万能头文件,会加长程序时间
  2. using namespace std;
  3. #define N 1000001//题目100%例化范围要求
  4. int a[N],b[N],c[N];//对照组和两个判断组
  5. int m,i,j,k,magic,nums,nums1,maxz;
  6. int main(){
  7. cin>>m;//输入传送门个数
  8. for(i=1;i<=m;i++)//按顺序摆布传送阵
  9. scanf("%d",&a[i]);
  10. for(k=1;k<=m;k++){//遍历循环,每个位置都要做一遍起始位置
  11. magic=1,i=k,nums=0,nums1=0;
  12. while(!b[i]||magic){//判断条件:当前位置没有来过且没使用魔法两者满足其一就继续
  13. if(b[a[i]]&&magic){//判断条件:当前位置已经来过但是没有使用魔法,两者同时满足
  14. if(i==m)break;//向右移动时如果是末尾则不可能,直接结束循环,转向左循环
  15. magic=0;//使用魔力,魔力清零
  16. printf("%d(使用了魔法向右移动)->",i);
  17. if(b[i]);//如果当前位置为死循环,那么不算入次数中,因为死循环在实际运行中会多加一次
  18. else nums++;//移动后没有重复,门数加一
  19. i++;
  20. if(b[i])break;//使用魔力移动后如果该位置重复,将会结束循环
  21. }
  22. else{//没有来过的位置,都按常规处理
  23. b[i]++;//累加标记
  24. nums++;//门数加一
  25. j=i;//上一次经过的传送门
  26. i=a[i];//从i门前往到下一个传送门
  27. printf("%d->",j);//输出上一个传送点
  28. }
  29. }
  30. printf("%d 共找到%d个\n",i,nums);//结束循环并输出寻找个数
  31. magic=1,i=k;//魔力和i门重置
  32. while(!c[i]||magic){//代码同上,没有大变化,b数组改为c数组
  33. if(c[a[i]]&&magic){
  34. if(i==1)break;
  35. magic=0;
  36. printf("%d(使用了魔法向左移动)->",i);
  37. if(c[i]);
  38. else nums1++;
  39. i--;
  40. if(c[i])break;
  41. }
  42. else{
  43. c[i]++;
  44. nums1++;
  45. j=i;
  46. i=a[i];
  47. printf("%d->",j);
  48. }
  49. }
  50. printf("%d 共找到%d个\n\n",i,nums1);
  51. maxz=max(maxz,max(nums,nums1));//两种状况取较大者,所有状况取最大者
  52. fill(b,b+1000001,0);//fill函数对b和c数组清零重置
  53. fill(c,c+1000001,0);
  54. }
  55. printf("\n最多能达到不同的魔法阵个数为 %d个",maxz);//输出最终结果
  56. return 0;
  57. }

输出结果

  1. 5
  2. 2 1 5 4 3
  3. 1->2(使用了魔法向右移动)->3->5->3 共找到4
  4. 1->2(使用了魔法向左移动)->1 共找到2
  5. 2->1(使用了魔法向右移动)->2 共找到2
  6. 2->1 共找到1
  7. 3->5 共找到1
  8. 3->5(使用了魔法向左移动)->4->4 共找到3
  9. 4->4(使用了魔法向右移动)->5->3->5 共找到3
  10. 4->4(使用了魔法向左移动)->3->5->3 共找到3
  11. 5->3(使用了魔法向右移动)->4->4 共找到3
  12. 5->3(使用了魔法向左移动)->2->1->2 共找到4
  13. 最多能达到不同的魔法阵个数为 4

博主这里将原来的题目可视化了,如果只需要实现题目所要求的功能,可以把所有的字符输出给删去。

三、经验总结

       在分析应用类题行时通常会有特殊情况,可以先找出普遍的情况,再去思考怎么对待特殊情况,讲一个问题反复拆解,不断细分成一小块一小块的,再将逻辑组合起来,这样出来的代码可能不是最优的,但是不会有错误的。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/924509
推荐阅读
相关标签
  

闽ICP备14008679号