C语言基础应用实战:(详解实际问题以巩固C语言基础知识)——开灯问题:有n盏灯,编号为1~n。第1个人把所有灯打开,第2个人按下所有编号为2的倍数的开关(这些灯将被关掉)

该程序解决了一个数学问题,即有n盏灯在经过k个人的操作后,哪些灯是亮着的。每个人按照编号对灯进行开关操作,最终输出灯的状态。代码使用C语言编写,通过for循环和数组来跟踪每个灯的状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

👋 欢迎来到“C语言基础应用实战”篇!

在这一篇博客中,我们将通过一个经典的编程问题来巩固你对数组、循环和条件语句的理解:开灯问题。问题的核心是模拟一个操作过程,并通过逻辑推导来得出最终的状态。通过这个问题,你将学会如何使用 C 语言来解决这类有规律的模拟问题。


 

🌟 问题描述

有 n 盏灯,编号为 1~n,初始时所有灯都关闭。现在有 k 个人依次进行操作。每个操作的规则是:

1. 第 1 个人将所有灯打开。
2. 第 2 个人按下所有编号为 2 的倍数的开关(这些灯将被关掉)。
3. 第 3 个人按下所有编号为 3 的倍数的开关(其中关掉的灯将被打开,开着的灯将被关闭)。
4. 依此类推,第 i 个人按下所有编号为 i 的倍数的开关。

最终,我们需要输出哪些灯是开着的。

输入

  • 两个整数 n 和 k,其中 1 ≤ k < n ≤ 1000,分别表示灯的数量和进行操作的人数。

输出

  • 输出所有开着的灯的编号,按升序排列。

🎯 解题思路

这个问题的核心是模拟多次开关操作。我们可以使用一个长度为 n 的数组来表示每盏灯的开关状态。初始化时,所有灯的状态为 0(表示关),然后根据规则模拟每个人的操作。

1. 数组表示灯的状态

我们使用一个数组 `a[]` 来存储灯的状态,数组的每个元素代表一个灯:

  • `a[i]` 为 1 时表示第 i 盏灯是开的;
  • `a[i]` 为 0 时表示第 i 盏灯是关的。

2. 每个人的操作

  • 第 1 个人将所有灯打开,所以我们把数组中的每个元素都设置为 1。
  • 对于第 i 个人,他会操作所有编号为 i 的倍数的灯(即 `j % i == 0` 的灯),对这些灯执行“开关”操作。
  • 我们可以通过 `a[j] = !a[j]` 来实现开关操作(若灯是关的,变为开;若灯是开的,变为关)。

3. 输出开着的灯

最后,我们遍历数组,输出所有值为 1 的灯的编号。


📝 代码实现

#include <stdio.h>
#include <string.h>

#define N 1010  // 定义数组的最大长度

int a[N];  // 存储灯的状态

int main() {
    int n, k, first = 1;
    scanf("%d%d", &n, &k);  // 输入灯的数量和进行操作的人数

    memset(a, 0, sizeof(a));  // 初始化灯状态数组,0表示关

    // 模拟每个人的操作
    for (int i = 1; i <= k; i++) {
        for (int j = 1; j <= n; j++) {
            if (j % i == 0) {
                a[j] = !a[j];  // 每次按下开关,改变灯的状态
            }
        }
    }

    // 输出所有开着的灯
    for (int i = 1; i <= n; i++) {
        if (a[i]) {  // 如果灯是开的
            if (first) {
                first = 0;  // 第一个输出前不加空格
            } else {
                printf(" ");  // 后续输出灯号时前面加空格
            }
            printf("%d", i);  // 输出开着的灯的编号
        }
    }

    return 0;
}

💡 代码分析

1. 初始化数组

memset(a, 0, sizeof(a));  // 将数组 a 中的所有元素初始化为 0,表示所有灯最开始都是关的

tips:使用 `memset` 函数来初始化数组,这是一个简洁且高效的做法。`memset` 会将数组中的所有字节设置为给定的值,这里将所有元素初始化为 0。

2. 模拟开关操作

a[j] = !a[j];  // 如果灯是关的,变为开;如果灯是开的,变为关

通过对每个操作进行模拟,当编号为 `i` 的人按下开关时,我们对所有编号为 `i` 的倍数的灯执行切换操作。

3. 输出开着的灯

if (first) {
    first = 0;  // 第一个输出时不加空格
} else {
    printf(" ");  // 后续输出时加空格
}

使用 `first` 标志量来控制是否在输出灯编号前加空格,确保输出的格式符合要求,即灯编号之间用空格分隔。


🏆 复杂度分析

  • 时间复杂度O(n \times k):每个人的操作需要遍历 `n` 个灯,总共进行 `k` 轮操作。
  • 空间复杂度O(n):我们使用了一个大小为 `n` 的数组来存储每盏灯的状态。

由于 `n` 和 `k` 都小于 1000,因此这个算法的效率是可以接受的。


🌟 示例

输入

7 3

输出

1 5 6 7

解析

  • 第 1 个人操作:所有灯都打开。
  • 第 2 个人操作:关闭编号为 2 的倍数的灯(2, 4, 6)。
  • 第 3 个人操作:切换编号为 3 的倍数的灯(3, 6)。

最终,开着的灯编号为 1, 5, 6, 7。


✨ 总结

1. 开关问题的模拟:通过数组模拟每个灯的开关操作,可以清晰地理解问题的解决过程。
2. 标志量的使用:在输出时使用标志量控制格式,避免不必要的空格。
3. 效率分析:通过分析时间复杂度和空间复杂度,确认了该算法适用于题目中给出的范围。


✍️ 读者互动

  • 👉 你认为如果增加更多的人操作,算法是否还有效?
  • 👉 你有没有思考过其他可能的优化方式?

欢迎在评论区分享你的想法和优化方案!让我们一起深入探讨有趣的编程问题! 🤝
 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Huazzi_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值