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),所以最终开着的门就是完全平方数 。但你这道题中 m 和 n 可以不同,当 m < n 时,大编号的灯可能因子不全被操作,结果就不是简单的完全平方数了。


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

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

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

相关推荐
让学习成为一种生活方式6 小时前
PlantCellChat--单细胞与空间转录组数据预测植物细胞间通讯的R包--The Plant Journal
算法
梅雅达编程笔记6 小时前
04-Python CSV数据保存与翻页抓取
开发语言·爬虫·python·pandas·数据采集·csv
C++ 老炮儿的技术栈6 小时前
有符号变量与无符号变量的区别
服务器·开发语言·前端·数据结构·c++·算法·c
估值探索者6 小时前
【Python量化策略实战 #04】Donchian 通道假突破太多?用 ATR 阈值过滤跑通真实突破信号
开发语言·python·接口·api接口·数据api接口·股票数据api接口
个 人 练 习 生6 小时前
C++:模板初阶详解
开发语言·c++·经验分享·学习
梦帮科技6 小时前
多任务权重流形融合:SLERP 球形线性插值、DARE 稀疏剪枝与多专家模型融合落地
人工智能·深度学习·算法·机器学习·tensorflow·聚类·剪枝
Tisfy6 小时前
LeetCode 0020.有效的括号:栈匹配
算法·leetcode·字符串·题解·括号匹配
知识分享小能手6 小时前
C++ 学习教程,从入门到精通,C++类继承 — 完整知识点详解(13)
开发语言·c++·学习
YYRAN_ZZU6 小时前
如何编一个gcc的交叉工具链
linux·c++
弹简特6 小时前
【Java项目-企悦抽】15-活动管理模块02-活动创建测试与活动列表实现
java·开发语言·springboot