开关灯

GitHub跳转原题关系图返回列表

把三类操作统一成翻倍数,按人只翻 k,2k,3k,… 模拟,输出被翻奇数次的灯。

OJ: roj

题目 ID: 1109

难度:入门

标签:模拟数论

创建: 2026-09-29 19:15

更新: 2026-10-05 01:40

题目描述

有 NN 盏编号 1…N1\ldots N 的灯,初始全部开启。MM 个人按编号从小到大操作:1 号关掉全部灯,2 号打开编号为 2 的倍数的灯,k (k≥3)k\ (k\ge 3) 号把 kk 的倍数取反。操作完后从小到大输出所有仍然关闭的灯的编号,用逗号分隔。N,M≤5000N,M\le 5000。样例输入 10 10,输出 1,4,9。

思路

初始全亮让三类操作统一为“第 kk 个人翻转 kk 的倍数”,无需分支;循环直接落在倍数 k,2k,3k,…k,2k,3k,\ldots 上做异或,总翻转次数即调和级数 O(Nlog⁡M)O(N\log M),最后输出被翻过奇数次的灯。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-29 19:15
# update_at: 2026-09-29 19:15

import sys


def solve() -> None:
    n, m = map(int, sys.stdin.buffer.read().split())

    # off[j] = 1 表示灯 j 被翻过奇数次(初始全亮,翻奇数次即最终关闭)。
    # 1 号"全部关闭"、2 号"打开 2 的倍数"本质都是取反,统一按翻倍数处理。
    off = bytearray(n + 1)
    for d in range(1, m + 1):
        off[d::d] = bytes(v ^ 1 for v in off[d::d])  # 下标 d, 2d, 3d, ... 恰是 d 的倍数

    print(','.join(map(str, (j for j in range(1, n + 1) if off[j]))))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 01:40
 * update_at: 2026-10-05 01:40
 */

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = 5005;

int n, m;
// off[j] = 1 表示灯 j 被翻过奇数次(初始全亮,翻奇数次即最终关闭)。
int off[MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    if (!(cin >> n >> m)) return 0;

    // 1 号"全部关闭"、2 号"打开 2 的倍数"、k>=3 号"取反 k 的倍数",
    // 三类操作在"初始全亮"下都等价于:把该人编号的倍数翻转一次。
    // 循环直接落在倍数上,省掉 j % k == 0 的逐灯判断。
    for (int d = 1; d <= m; d++) {
        for (int j = d; j <= n; j += d) {
            off[j] ^= 1;
        }
    }

    bool first = true;
    for (int j = 1; j <= n; j++) {
        if (off[j]) {
            if (!first) cout << ',';
            cout << j;
            first = false;
        }
    }
    cout << '\n';
    return 0;
}