问题描述:
有n盏灯,编号为1~n,每个灯有个开关,按一下打开,再按一下关闭,再按一下打开。第1个人把所有灯打开,第2个人按下编号为2的倍数的灯的开关,第3个人按下编号为3的倍数的灯的开关,依此类推,一共有k个人,问最后有哪些灯开着?输入n和k,输出开着的灯的编号。k<=n<=1000。
- 第一种解法
即《算法竞赛入门经典》(第2版)的方法,将灯的状态存入一维数组中,设灯亮的状态为1,第一层循环为从第1个人到第k个人,第二层循环为在所有的n盏灯中,当第i个人按i的倍数的开关时,即灯的编号取模i为0,此时灯的状态取反。根据这个思想,核心代码为:
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)
if(j%i==0)
a[j]=!a[j];
此种方法简单明了,比较容易理解,但这种方法在第二层循环时,对于从1到n,先判断取模,为0再对该元素取反,是对该数组每个元素进行判断操作,时间上会长一些。*那有没有更快的解法呢? *
- 第二种解法
第1个人将灯的状态置1,在两层循环中第一层循环初始值从2开始,到第k个人; 第二层循环设变量j,变量增加为j+=i,即第m个人按开关时,不用在从1到n的数组中逐个判断该数组元素下标与m的取模,而是通过变量j+=m直接对数组元素下标是m的倍数的元素进行取反,其余元素直接忽略,在计算上会节省很多时间。可以想像的是,随着第m个人逐渐逼近k,则计算量会更加减少。核心代码如下:
for(int i=2;i<=k;i++)
for(int j=i;j<=n;j+=i)
a[j]=!a[j];
这种方法理解上可能稍复杂一点,但计算量及时间会减少很多,所以多一下思考,多一种解法,对于算法竞赛训练,是很有效果的。