目录
模拟的概念
例1:开关灯
算法思路:
代码如下:
输入输出:
例2:序列操作和查询
算法思路:
代码如下:
输入输出:
例3:数组折叠
算法思路:
代码如下:
例4:数字消除
算法思路
代码如下:
作业1:角谷猜想
作业2:校门外的树
作业3:乒乓球
模拟的概念
模拟算法就是模拟题目给的操作,用代码一步一步的描述出来即可。在过程中使用的都是我们已知的各种方法,如数组元素调用、排序、枚举等等,只是这些过程一般比较复杂。本次课程主要针对一维数组的模拟。
在各类算法竞赛中,包括CSP-J/S,NOIP等竞赛,经常会出现各类“模拟题目”,遇到这种题大家不需要害怕,甚至可以将其作为“送分题”,因为你只需要按照题目叙述的方式来写程序就能得到最终答案。模拟不是一种算法,而是一种技巧,要想掌握模拟题目,就需要多读题、多整理细节问题。
例1:开关灯
【描述】有n盏灯,从1到N按顺序依次编号,初始时所有灯都处于开启状态;有m个人,从1到m依次编号。第一个人将灯全部关闭,第二个人将编号为2的倍数的灯打开,第三个人将编号为3的倍数的灯做相反处理(即将打开的灯关闭,将关闭的灯打开)。依照编号递增顺序,以后的人都一样,将凡是自己编号倍数的灯做相反处理。请问:当第m个人操作之后,哪几盏灯是关闭的,按从小到大输出其编号,用逗号间隔。
【输入】一行,n和m,空格隔开
【输出】顺次输出关闭的灯的编号,用逗号隔开
【输入样例】10 1010
【输出样例】1,4,9
算法思路:
这个问题是一个经典的编程问题,通常被称为“灯泡问题”或“约瑟夫环问题”的变体。问题的核心在于模拟每个人对灯的操作,并跟踪每盏灯的状态。以下是解决这个问题的详细思路:
1. 初始化
- 创建一个数组 `lights`,长度为 `n+1`(因为灯的编号从1到n),初始时所有灯都设置为开启状态(例如,可以用 `true` 表示开启)。
2. 模拟操作
- 对于每个人(从1到m),执行以下操作:
- 遍历所有灯,找到编号是当前人编号的倍数的灯。
- 切换这些灯的状态(如果灯是开启的,则关闭;如果灯是关闭的,则开启)。
3. 状态切换
- 灯的状态切换可以通过简单地取反数组中对应位置的值来实现。例如,如果 `lights[j]` 是 `true`,则将其设置为 `false`,反之亦然。
4. 收集结果
- 在所有人操作完成后,遍历 `lights` 数组,收集所有关闭的灯的编号。
5. 输出结果
- 将收集到的关闭的灯的编号按从小到大的顺序输出,编号之间用逗号隔开。
代码如下:
#include <iostream>
using namespace std;
int main(){bool lights[1000];int n,m;cin>>n>>m;for(int i=1;i<=n;i++){lights[i]=true;}for(int i=1;i<=m;i++){for(int j=i;j<=n;j=j+i)lights[j]=!lights[j];}int cnt=0;for(int i=1;i<=n;i++){if(!lights[i]){cnt++;if(cnt==1) cout<<i;else cout<<","<<i;} }
输入输出:
例2:序列操作和查询
【描述】现有一个长度为n的数组,对这个数组进行m次操作,可以对数组进行的操作分为以下三类:
输入1 i: 表示输出数组中第i个元素的值;
输入2 i v:表示在数组中第i个元素前加入新的元素v;
输入3 i:表示删除数组中的第i个元素。
注意:三类操作都要满足 i <= n。经过m轮操作后,输出的是哪些数字,每行一个数字。
【输入】第1行一个整数n,表示数组的初始长度;第2行是n个用空格间隔的数,表示原始的数组;第3行是整数m,表示操作次数。接下来m行是m次操作指令,每个指令一行(题目描述中的三类操作中的一种)。
【输出】对于第一种操作输出对应的答案,一行输出一个数。
算法思路:
对题目的要求一步一步的实行,先保证数组的输入以后,需要对三种情况进行分类处理。第一种处理里面有输出,后面两种都是在操作。操作的要点是数组的插入和删除。
插入的话,就要求插入位置后面所有数字向后移动一步,实现a[i+1]=a[i]的操作;
而删除则需要当前位置后面所有的数字向前移动一步,实现a[i]=a[i+1]。这里需要注意移动的方向,要从头移动。
代码如下:
using namespace std;
int main(){int a[1000];int n,m,p,q,v;cin>>n;for(int i=1;i<=n;i++)cin>>a[i];cin>>m;for(int i=1;i<=m;i++){cin>>p;if(p==1){cin>>q;cout<<a[q]<<endl;}else if(p==2){cin>>q>>v;for(int j=n;j>=q;j--)a[j+1]=a[j];a[q]=v;n++;}else{//p=3cin>>q;for(int j=q;j<n;j++)a[j]=a[j+1];n--;}}
}
输入输出:
例3:数组折叠
【描述】李雷和韩梅梅在玩数组折叠游戏,游戏规则是,给出n个整数,按照从左到右的顺序排列,现在需要将这列整数从中间折叠m次,右边的叠加到左边,每次折叠后,重合的两个数字会相加变成一个新的数字。请你输出折叠m次后的s数组。
【输入】第1行是整数n和m;第2行是数组中的n个整数【输出】1行。折叠m次后的数组元素
算法思路:
数组对折,需要把后半部分移动到前半部分对应位置进行数组相加,所以移动次数为n/2(即循环次数),然后需要进行的就是数组加法,最后要对数组长度也做n/2的操作,但是这里需要注意的是,如果长度是奇数不能只是简单的n/2哦,对称位置怎么找?
如果数组下标从1开始,那么第个元素的对称元素位置是谁? 找找规律:1对n;2对n-1;3对n-2;i对什么?
代码如下:
#include <iostream>
using namespace std;int main() {int a[1000]; // 假设数组最大长度为 1000int n, m;cin >> n >> m; // 输入数组长度和操作次数 for (int i = 0; i < n; i++) { // 输入数组元素cin >> a[i];}for (int j = 0; j < m; j++) { // 进行 m 次操作for (int i = 0; i < n / 2; i++) { // 遍历前半部分a[i] = a[i] + a[n - 1 - i]; // 将前半部分和后半部分对应元素相加}if (n % 2 == 1) {// 如果数组长度是奇数,中间元素保持不变n = n / 2 + 1;} else {n = n / 2;}}for (int i = 0; i < n; i++) { // 输出最终结果cout << a[i] << " ";}return 0;
}
例4:数字消除
【描述】李雷喜欢玩游戏,有一天他在电脑上发现 了一个叫“数字消消消”的游戏,其规则如下: 给定一个长度为n的整型数组,指定一个数a,如果该数组中有3个及3个以上的a连续出现,则该数字将会从数组中消除。
【输入】第1行是整数n和a;第2行是数组中的n个整数
【输出】1行。输出消除后的数组
【样例输入】 6 1
1 1 1 2 2 3
【样例输出】 2 2 3
算法思路
1、输入处理:
- 首先读取两个整数
n
和a
,分别表示数组的长度和需要移除的数字。 - 然后读取数组中的
n
个整数到数组arr
中。
2、双重循环检测:
-
使用一个外层循环
for(int i=0; i<n; i++)
遍历数组的每个元素。 -
对于每个元素,使用一个内层循环
for(int j=i; j<n; j++)
检查从当前元素开始的连续相同数字的数量。 -
如果当前元素
arr[j]
等于a
,则增加计数器num
;如果不等于a
,则跳出内层循环。
3、移除连续相同数字:
-
如果计数器
num
大于等于 3,说明从索引i
开始有连续三个或更多的a
,需要跳过这些元素。 -
使用
i=i+num-1
将外层循环的索引跳过这些连续的a
。 -
如果
num
小于 3,说明当前元素不是连续三个或更多的a
,需要输出该元素。
4、重置计数器:
-
每次完成一次内层循环后,重置计数器
num
为 0,以便下一次检测。
5、输出结果:
-
在外层循环中,对于不满足连续三个或更多
a
的元素,输出该元素。
代码如下:
#include <iostream>
using namespace std;
int main(){int n,a;int arr[1000];cin>>n>>a;for(int i=0;i<n;i++)cin>>arr[i];int num=0;for(int i=0;i<n;i++) {for(int j=i;j<n;j++){if(arr[j]==a)num++;elsebreak;}if(num>=3)i=i+num-1;elsecout<<arr[i]<<" ";num=0;}
}
作业1:角谷猜想
【描述】 所谓角谷猜想,是指对于任意一个正整数,如果是奇数,则乘3加1,如果是偶数,则除以2,得到的结果再按照上述规则重复处理,最终总能够得到1。如,假定初始整数为5,计算过程分别为16、8、4、2、1。 程序要求输入一个整数,将经过处理得到1的过程输出来。
【输入 】一个正整数N(N <= 2,000,000)
【输出】从输入整数到1的步骤,每一步为一行,每一部中描述计算过程。最后一行输出"End"。如果输入为1,直接输出"End"。
【样例输入】 5
【样例输出】 5*3+1=16
16/2=8
8/2=4
4/2=2
2/2=1
End
【提示】注意计算过程中中间值可能会超过int范围。
作业2:校门外的树
【描述】某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。
【输入】第一行有两个整数L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
对于20%的数据,区域之间没有重合的部分;
对于其它的数据,区域之间有重合的情况。【输出】包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
【样例输入】 500 3
150 300
100 200
470 471
【样例输出】 298
作业3:乒乓球
【题目背景】国际乒联现在主席沙拉拉自从上任以来就立志于推行一系列改革,以推动乒乓球运动在全球的普及。其中 11 分制改革引起了很大的争议,有一部分球员因为无法适应新规则只能选择退役。华华就是其中一位,他退役之后走上了乒乓球研究工作,意图弄明白 11 分制和 21 分制对选手的不同影响。在开展他的研究之前,他首先需要对他多年比赛的统计数据进行一些分析,所以需要你的帮忙。
【题目描述】 华华通过以下方式进行分析,首先将比赛每个球的胜负列成一张表,然后分别计算在 11 分制和 21 分制下,双方的比赛结果(截至记录末尾)。 比如现在有这么一份记录,(其中 W 表示华华获得一分,L 表示华华对手获得一分): WWWWWWWWWWWWWWWWWWWWWWLW
在 11 分制下,此时比赛的结果是华华第一局 11 比 0 获胜,第二局 11 比 0 获胜,正在进行第三局,当前比分 1 比 1。而在 21 分制下,此时比赛结果是华华第一局 21 比 0 获胜,正在进行第二局,比分 2 比 1。如果一局比赛刚开始,则此时比分为 0 比 0。直到分差大于或者等于 2,才一局结束。 注意:当一局比赛结束后,下一局立刻开始。 你的程序就是要对于一系列比赛信息的输入(WL 形式),输出正确的结果。
【输入格式】 每个输入文件包含若干行字符串,字符串由大写的 W 、 L 和 E 组成。其中 E 表示比赛信息结束,程序应该忽略 E 之后的所有内容。
【输出格式】 输出由两部分组成,每部分有若干行,每一行对应一局比赛的比分(按比赛信息输入顺序)。其中第一部分是 11 分制下的结果,第二部分是 21 分制下的结果,两部分之间由一个空行分隔。