m/2个人拉m盏灯后求灯的状态问题

m个人拉m盏灯后求灯的状态问题-CSDN博客中介绍了m个人拉m盏灯的问题。下边介绍一下m/2个人拉m盏灯的问题。

题目:

有m盏灯,编号分别为1,2,3,...,m,每拉一次灯的开关,灯的亮灭状态就发生一次变化。这m盏灯初始状态都是亮着的,有m/2个人去拉灯,第1个人把所有的灯都拉一次,第2个人把编号是3的倍数的灯拉都拉一次,第3个人把编号是5的倍数的灯都拉一次,...,第m/2个人把编号是(m/2)*2+1的灯都拉一次。问:最终哪些编号的灯是灭的?

本题和上一解题目的区别是:上一节的题目是求灯编号i的所有因数为奇数的情况,这个题目是找灯编号i的奇因数为奇数的情况。

一、编程方法一

我们先用上一节的编程方法一的思路来进行求解一下。

第一步:声明一个整形变量m,由键盘输入m的具体值。

第二步:声明一个有m+1个元素的布尔型数组,其中脚标为1到m的元素用于存储m盏灯的亮灭状态,亮为1,灭为0。

第三步:用for循环求解当所有人都拉过一次灯后,灯的状态。这一步是关键,灯的序号用i表示,人的序号用j表示,从i=1开始到m结束,循环求解j=1开始到m结束,增加步伐为2时(因为拉的灯的编号都为奇数的倍数),i是否是j的倍数,如果是则序号为i的灯状态翻转一次。

第四部:把状态为灭的灯的序号输出。

具体代码如下:

#include <iostream>
using namespace std;
 
int main()
{
    int m;    //声明m盏灯
    cin>>m;    //键盘输入m的具体数值    
    bool lamp_status[m+1];    //声明存储灯状态的布尔型变量数组,1为亮,0为灭
 
    for(int i=1;i<=m;i++)    //循环求解i盏灯的状态
        {
            lamp_status[i]=1;    //灯的初始状态为亮
            for(int j=1;j<=m;j+=2)    //循环求解第j个人拉灯之后,灯的状态变化,j只能为奇数,所以增加步伐为2
                {
                    if(i%j==0)        //如果i是j的倍数,则灯的状态进行翻转
                    lamp_status[i]=!lamp_status[i];
                }
                
            if(lamp_status[i]==0)     //如果灯的状态为灭则输出灯的序号
                {
                    cout<<i<<endl;
                }
        } 
    return 0;
}

当m=100时,运算结果为:

1
2
4
8
9
16
18
25
32
36
49
50
64
72
81
98
100

观察发现,灯灭的编号比上一节的数量要多,但编号中仍然包含了所有的100以内的完全平方数{1,4,9,16,25,36,49,64,81,100}和非完全平方数{2,8,18,32,50,72,98}。再仔细观察,发现了一个更有趣的现象,这些非完全平方数恰好是前7个完全平方数{1,4,9,16,25,36,49}的2倍。

为了寻找规律,把m值增大到100,看以下运行结果。

1
2
4
8
9
16
18
25
32
36
49
50
64
72
81
98
100
121
128
144
162
169
196
200
225
242
256
288
289
324
338
361
392
400
441
450
484
512
529
576
578
625
648
676
722
729
784
800
841
882
900
961
968

可以发现,结果仍然是有两部分组成,一部分是完全平方数{1,4,9,16,25,36,49,64,81,100,121,144,169,196,225,256,289,324,361,400,441,484,529,576,625,676,729,784,841,900,961},另外一部分是非完全平方数的2倍{2,8,18,32,50,72,98,128,162,200,242,288,338,392,450,512,578,648,722,800,882,968}。

看来这也是一个必然的规律,那么我下边还是来分析一下。

二、理论分析

1、首先来分析,为什么完全平方数的奇因数为奇数个

(1)第一种情况,完全平方数为奇数。

奇数的所有因数都是奇数,上一届分析过了,完全平方数的因数个数为奇数,也就是完全平方数为奇数时,其奇因数的个数一定是奇数。所以得出:

规律1:当完全平方数为奇数时,这个完全平方数的奇因数的个数为奇数。

(2)第二种情况,完全平方数为偶数。

假设这个偶数为i,它的平方根为i_{0},假设i_{0}可以分解为s个奇质因数和t个偶质因数之积,即i_{0}=\prod_{j=1}^{j=s}i_{0j}*\prod_{k=1}^{k=t}i_{0k},令i_{s}=\prod_{j=1}^{j=s}i_{0j}i_{t}=\prod_{k=1}^{k=t}i_{0k},则i=(i_{0})^{2}=(i_{s})^{2}*(i_{t})^{2}。如果i存在奇因数,那么这个奇因数一定是(i_{s})^{2}的因数,而(i_{s})^{2}是一个完全平方数,而且是奇数,所以根据规律1,i的奇因数,也就是(i_{s})^{2}的奇因数的个数一定是奇数。

所以综合以上两种情况得出:

规律2:完全平方数的奇因数的个数为奇数。

2、再来分析,完全平方数i的2倍的奇因数的个数

完全平方数i的2倍=2*i,2为偶数,且2中不包含奇因数,因此2*i中奇因数只与i有关,其奇因数的个数就是i的奇因数的个数。根据规律2,得知完全平方数的2倍的奇因数个数也为奇数。

同理,可以推测,一个完全平方数i乘以一个系数k,如果这个k的质因数中不包含奇数,那么k*i的奇因数的个数也一定是奇数。而符合这个条件的k只能是2的若干次幂,即k=2^{p}(p为大于等于1的自然数)。如果p为偶数,那么k*i=(2^{p/2})^{2}*i=(2^{p/2}*i_{0})^{2},仍然为一个平方数。如果p为奇数,那么k*i=(2^{(p-1)/2})^{2}*i*2=(2^{(p-1)/2}*i_{0})^{2}*2,其结果为一个完全平方数的2倍。

根据以上规律,可以得知,小于m的自然数中,其奇因数为奇数的情况只包括完全平方数和完全平方数的2倍这两种情况。这也与程序运算得出的结果相一致。

三、编程方法二

四、编程方法三

既然这样,那么我们就可以放心大胆地利用上一节的编程方法三来求解本节的问题了。

思路大致如下:

第一步,求出1到m内最大完全平方数对应的最大平方根。

第二步,求出1到m内的所有完全平方数。

第三步,求出1到m内的所有完全平方数的2倍。

第四步,输出所有符合要求的结果。

#include <iostream>
#include <math.h>
using namespace std;
 
int main()
{
    int m;    //声明m盏灯
    cin>>m;    //键盘输入m的具体数值
    int MaxsquarRoot=sqrt(m);       //求平方数为m的最大整数平方根,那么小于等于这个最大整数平方根的所有自然数的平方都是小于m的完全平方数。
    for(int i=1;i<=MaxsquarRoot;i++)    //循环求解i盏灯的状态
        {
            cout<<i*i<<endl;    //输出完全平方数
            if(i*i*2<=m)
            cout<<i*i*2<<endl;   //输出小于等于m的完全平方数的2倍
        } 
    return 0;
}

当m=100时,运行的结果如下:

1
2
4
8
9
18
16
32
25
50
36
72
49
98
64
81
100

运算结果与编程方法一一致,只是数字的排列顺序有些差异,有兴趣的C粉,可以把我的程序改进一下,能按由小到大的顺序输出。

(全文完)

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 题目假设有n个盏灯(n为不大于5000的正整数),从1到n按顺序编号,初始时全部处于开启状态;同时有m个人(m为不大于n的正整数),从1到m依次编号。每个人依次进行操作,第i个人将所有编号为i的倍数的灯的开关状态进行一次取反操作(即开启的变为关闭,关闭的变为开启)。 ### 回答2: 每个人可以按照以下规则对灯进行操作: 1. 第i个人只对所有序号为i的倍数的灯进行操作(包括打开和关闭); 2. 如果灯原来是开着的,那么第i个人对它进行一次操作后,它就会关闭;如果它原来是关着的,那么第i个人对它进行一次操作后,它就会打开。 问最终有多少盏灯是开着的。 首先需要明确的是,在操作灯的过程中,若一个数i的因子个数为奇数,则它最后状态会变为开启,反之,若它的因子个数为偶数,则最后状态会为关闭。因为一个数的因子总是成对出现的,除了平方数,它自身作为因子只会算一次。这是实际问题中的一个数学规律。 所以我们只需要确定1到n每个数的因子个数,即可得到最终结果。可以采用暴力的方法,在1到n循环,依次确定每个数的因子个数。时间复杂度为O(n^2)。但是此时n最大为5000,效率会比较低。 更高效的方法是采用线性筛法求1到n每个数的因子个数。首先,每个数a的因子个数一定等于它的因子b的因子个数加1,其中b为a的质因子。所以,我们可以在筛素数的同时,预处理出每个数i的最小质因子p[i],以及它生成的后继数p[i]*j(j为i的不包括p[i]因子的因子)的最小质因子p[i*j],再根据上述规律求出每个数的因子个数。 最后,根据得到的因子个数统计开着的灯的个数即可。 代码如下: ### 回答3: 每个人按顺序来到这些灯前,会按照以下规则对部分灯进行开关操作: 1. 第一个人将所有编号为奇数的灯关闭; 2. 第二个人将所有编号为3的倍数的灯进行开关操作:闭合状态则打开,打开状态则关闭; 3. 第三个人将所有编号为4的倍数的灯进行开关操作:闭合状态则打开,打开状态则关闭; 4. 以此类推,第k个人依次对所有编号为k的倍数的灯进行开关操作,即闭合状态则打开,打开状态则关闭; 最后统计还有多少盏灯处于打开状态。 解题思路: 这道题先注意读题,其他博客上方法很多其实都编到了题目中,甚至题目所强调的m<=n都没看到(我翻了两页没看到)。 正题:这道题有一重循环肯定是跑不掉的,即m个人依次对每个编号为k的倍数的灯进行开关操作的这个循环。 现在不处理k和m,我们先把步骤3解析一下,编号为4的倍数的灯进行开关操作。它本来就是开的,变成闭的;是闭的,这里变成开的,开关相反。类似题目中快排的标杆左右划分,以下称为原始开关状态。 其原理是,一次开关操作后,编号为4倍数+1的灯的状态会被改变,编号为8倍数+1的灯的状态又会被改变一次,编号为12倍数+1的灯的状态又会被改变一次。经过检查得知,K倍数+1也一样。所以,每到一个人,1~K-1号灯的亮灭状态是不会改变的。 于是可以把1~n灯的原始开关状态预处理出来,用bool类型存储。 以上面编号为4的情况作为例子: $ \begin{array}{|c|c|c|c|c|c|c|c|c|} \hline 1 &2 &3 &4 &5 &6 &7 &8 &9 \\ \hline \texttt{O} & \texttt{O} &\texttt{O} &X &\texttt{O} &\texttt{O} &\texttt{O} &X &\texttt{O}\\ \hline \end{array} $ O为初始化开的,X为初始化关的,1~3号状态不变,4、8状态变化,5、9依然是开着的。 再从1到m枚举每个人,将其负责的灯编号以及它的倍数对应状态进行转化,转化方式由原始开关状态决定,用异或运算符^表示: bool[i]=bool[i]^1 //将灯的开关状态取反 获取数组的某个元素的状态就能用bool[n]的形式来操作。因为排除了不需要转化的部分,不需要枚举1~k-1,所以最后只需要统计bool数组中为true的元素个数即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值