赞
踩
前言:本道题目难度一般,当时博主的主要思路和复盘的时候差不多,但是比赛的时候漏了某些特殊情况,导致本题 “ 全盘崩溃 ”。复盘的是也是发现了这些问题,又因为是秋后算账,怎么来都无所谓,所以博主在本题的解法上加了些自己的 “ 私活 ”
(为了让题目更直观),接下来是本道题的解析。
一、题目分析
提取题目的关键信息: 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个
博主这里将原来的题目可视化了,如果只需要实现题目所要求的功能,可以把所有的字符输出给删去。
三、经验总结
在分析应用类题行时通常会有特殊情况,可以先找出普遍的情况,再去思考怎么对待特殊情况,讲一个问题反复拆解,不断细分成一小块一小块的,再将逻辑组合起来,这样出来的代码可能不是最优的,但是不会有错误的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。