C++信奥:开关门、开关灯问题

🚪 "开关门"问题

彭彭老师

这道题是经典的"因子与完全平方数"问题,关键是从动手模拟到发现规律


第一步:动手模拟,建立直觉

小数据亲手模拟一遍,比如 n=10。

画一张表格,用 ✅ 表示开,❌ 表示关:

房间号 1 2 3 4 5 6 7 8 9 10
初始状态
服务员1(全开)
服务员2(2的倍数翻转)
服务员3(3的倍数翻转)
服务员4(4的倍数翻转)
服务员5(5的倍数翻转)
服务员6
服务员7
服务员8
服务员9
服务员10

💡 自己数一数:最终打开的是哪些房间?(答案:1、4、9)


第二步:"为什么"

问一个关键问题:

"房间6被哪些服务员碰过?"

列出来:

  • 服务员1(1×6=6)
  • 服务员2(2×3=6)
  • 服务员3(3×2=6)
  • 服务员6(6×1=6)

一共被碰了 4次 (偶数次),所以最终是关着的

再问:

"房间4被哪些服务员碰过?"

  • 服务员1(1×4=4)
  • 服务员2(2×2=4)
  • 服务员4(4×1=4)

一共被碰了 3次 (奇数次),所以最终是开着的


第三步:总结规律------找"朋友配对"

这是最关键的一步:

每个房间号就像要找"乘法好朋友"。比如房间12的好朋友有:

  • 1 和 12(1×12=12)
  • 2 和 6(2×6=12)
  • 3 和 4(3×4=12)

好朋友总是成对出现 的,所以大多数房间被碰偶数次,最后关上了。

但是!有一种特殊情况------自己和自己配对

  • 房间4:2×2=4,2只能和自己配对,没有别人了
  • 房间9:3×3=9,3只能和自己配对
  • 房间16:4×4=16

这些房间多出来一次,总共被碰奇数次 ,所以最后开着

用一句话总结:

🎯 只有"自己×自己"的房间号(也就是1、4、9、16、25......),门才是开着的。


第四步:联系代码,理解实现

cpp 复制代码
// 外层循环:第i个服务员
for (int i = 1; i <= n; i++) {
    // 内层循环:处理i的倍数(即i能整除的房间)
    for (int j = i; j <= n; j += i) {
        door[j] = 1 - door[j]; // 翻转状态
    }
}

其实我们发现了规律,可以直接写更简单的代码!

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    
    bool first = true;
    for (int i = 1; i * i <= n; i++) {
        if (!first) cout << " ";
        cout << i * i;
        first = false;
    }
    cout << endl;
    
    return 0;
}

开关灯问题和开关门问题本质上是同一道题,只是换了个"故事外壳"而已。


两者的对比

开关门问题 开关灯问题
对象 房间的门 走廊的灯
初始状态 门关着 灯灭着
操作 第i个人翻转编号为i的倍数的门 第i个人翻转编号为i的倍数的灯
最终问题 哪些门开着 哪些灯亮着

核心逻辑完全一样:

  • 每个门/灯被翻转的次数 = 它的因子个数
  • 因子个数为奇数 → 最终是"开/亮"
  • 只有完全平方数的因子个数是奇数(因为有一个因子"自己×自己",无法配对)

所以结论也一样:最终亮着的灯(或开着的门)就是 1, 4, 9, 16, 25, 36......


"你们发现了吗?不管是开关门还是开关灯,其实都是同一个问题!就像数学里的'鸡兔同笼',题目可以编出很多故事,但解题方法是一样的。"

可以用简单的数学方法

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin>>n;
    for(int i=1;i*i<=n;i++){
    	cout<<i*i<<" ";
	}
	
    return 0;
}

也可以用模拟

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

bool lights[1005]={false};

int main() {
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
     	for(int j=i;j<=n;j+=i){
     		lights[j]=!lights[j];//切换灯的状态 
		}
	}
     //输出灯
	bool f = true;
	for(int i=1;i<=n;i++){
	 	if(lights[i]){
	 		if(!f)cout<<" ";
	 		cout<<i;
	 		f=false;
	 	}
	} 
	
    return 0;
}
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n,m;
    cin>>n>>m;
    bool arr[5005]={false};//灯初始全开
	//模拟m个人操作
	for(int i=1;i<=m;i++){
		for(int j=i;j<=n;j+=i){
			arr[j] = !arr[j];//切换状态 
		}
	} 
	//输出关闭的灯
	bool f=true;
	for(int i=1;i<=n;i++){
		if(arr[i]){
			if(!f)cout<<",";//逗号输出法
			cout<<i;
			f=false; 
		}
	} 
    
    return 0;
}

常见的"开关灯"和"开关门"问题通常有以下几种区别:

常见的差异点

对比项 开关灯问题 开关门问题
初始状态 通常初始全亮(开) 通常初始全关(关)
最终输出 输出关着的编号 输出开着的编号
操作人数 有n盏灯、m个人(m可能≠n) 通常n扇门、n个人(一一对应)

核心区别

  1. 初始状态相反 → 最终结果相反。灯初始全开,被切换奇数次的灯最终是 的;门初始全关,被切换奇数次的门最终是的。

  2. 操作人数是否等于总数 → 经典开关门问题中,人和门数量相同(都是n),所以最终开着的门就是完全平方数 。但你这道题中 mn 可以不同,当 m < n 时,大编号的灯可能因子不全被操作,结果就不是简单的完全平方数了。


当 m 可能 ≠ n 时,建议直接用模拟法。 核心区别如下:

  • 开关门(m = n) :所有因子都在操作范围内,被切换奇数次的门一定是完全平方数,可直接输出。
  • 开关灯(m ≠ n) :当 m < n 时,大编号灯的因子可能超出 m 的范围,导致操作次数奇偶性改变,数学规律失效。此时必须用模拟法,按题意逐人逐灯切换状态。

数学做法的适用边界 :只有当 m ≥ n(操作人数 ≥ 灯数)时,所有因子都能被操作到,数学结论才成立。只要 m < n,就必须老老实实模拟。

相关推荐
冻柠檬飞冰走茶1 小时前
PTA基础编程题目集 7-31 字符串循环左移(C语言实现)
c语言·开发语言·数据结构·算法
Hi李耶1 小时前
【LeetCode】4-寻找两个正序数组的中位数
算法·leetcode·职场和发展
图灵机z2 小时前
【算法提高课】AcWing 1027. 方格取数 长期攻克提高课-day3(3/219)
算法
hans汉斯2 小时前
计算机科学与应用|改进MeanShift算法在智能监控视频中的应用研究
图像处理·人工智能·功能测试·深度学习·算法·音视频
a1117762 小时前
中文优先的企业 RAG 知识库 开源项目
开发语言·开源·kotlin
2301_777998342 小时前
C/C++:预处理详解
c语言·c++
破z晓2 小时前
javascript 导出excel表
开发语言·javascript·excel
t-think3 小时前
C++类和对象详解(一)
开发语言·c++
西门啐血3 小时前
上位机开发之假装有设备,使用 C# 模拟串口设备
开发语言·mongodb·c#