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

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

一、题目分析

       提取题目的关键信息: 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个位置,赋予的值)

输入

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

对a数组输出

 0 0 0 0 0

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

二、代码实现

#include<bits/stdc++.h>//万能头文件,会加长程序时间
using namespace std;
#define N 1000001//题目100%例化范围要求
int a[N],b[N],c[N];//对照组和两个判断组
int m,i,j,k,magic,nums,nums1,maxz;
int main(){
	cin>>m;//输入传送门个数
	for(i=1;i<=m;i++)//按顺序摆布传送阵
	scanf("%d",&a[i]);
	for(k=1;k<=m;k++){//遍历循环,每个位置都要做一遍起始位置
    	magic=1,i=k,nums=0,nums1=0;
   		while(!b[i]||magic){//判断条件:当前位置没有来过且没使用魔法两者满足其一就继续
			if(b[a[i]]&&magic){//判断条件:当前位置已经来过但是没有使用魔法,两者同时满足
				if(i==m)break;//向右移动时如果是末尾则不可能,直接结束循环,转向左循环
		    	magic=0;//使用魔力,魔力清零
				printf("%d(使用了魔法向右移动)->",i);
				if(b[i]);//如果当前位置为死循环,那么不算入次数中,因为死循环在实际运行中会多加一次
				else nums++;//移动后没有重复,门数加一
		    	i++;
				if(b[i])break;//使用魔力移动后如果该位置重复,将会结束循环	
			}
			else{//没有来过的位置,都按常规处理
		    	b[i]++;//累加标记
		    	nums++;//门数加一
		    	j=i;//上一次经过的传送门
		    	i=a[i];//从i门前往到下一个传送门
				printf("%d->",j);//输出上一个传送点
			}
		}
		printf("%d    共找到%d个\n",i,nums);//结束循环并输出寻找个数
		magic=1,i=k;//魔力和i门重置
		while(!c[i]||magic){//代码同上,没有大变化,b数组改为c数组
			if(c[a[i]]&&magic){
				if(i==1)break;
		    	magic=0;
				printf("%d(使用了魔法向左移动)->",i);
				if(c[i]);
				else nums1++;
		    	i--;
				if(c[i])break;				
			}
			else{
		    	c[i]++;
		    	nums1++;
		    	j=i;
		    	i=a[i];
				printf("%d->",j);
			}
		}	
		printf("%d    共找到%d个\n\n",i,nums1);
		maxz=max(maxz,max(nums,nums1));//两种状况取较大者,所有状况取最大者
		fill(b,b+1000001,0);//fill函数对b和c数组清零重置
		fill(c,c+1000001,0);       
	}
	printf("\n最多能达到不同的魔法阵个数为 %d个",maxz);//输出最终结果
	return 0;
}

输出结果

5
2 1 5 4 3
1->2(使用了魔法向右移动)->3->5->3    共找到4个
1->2(使用了魔法向左移动)->1    共找到2个

2->1(使用了魔法向右移动)->2    共找到2个
2->1    共找到1个

3->5    共找到1个
3->5(使用了魔法向左移动)->4->4    共找到3个

4->4(使用了魔法向右移动)->5->3->5    共找到3个
4->4(使用了魔法向左移动)->3->5->3    共找到3个

5->3(使用了魔法向右移动)->4->4    共找到3个
5->3(使用了魔法向左移动)->2->1->2    共找到4个


最多能达到不同的魔法阵个数为 4个

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

三、经验总结

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

 

  • 44
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值