1 条题解
-
0
题解: Multiple Lamps
问题简述
有 盏灯,初始全灭。按钮 会切换所有编号为 的倍数的灯。
需要按至少一个按钮(每个最多一次),且满足 条约束:若按了 ,则必须按 。
要求最终亮灯数 ,输出任意一组按下的按钮,或 表示无解。关键观察
-
按下所有按钮后的结果
灯 被所有 的约数按钮切换。
它最终亮着的充要条件是 的约数个数为奇数,即 是完全平方数。
所以亮灯数为 。 -
何时直接按所有按钮可行
需要 $\lfloor \sqrt{n} \rfloor \le \left\lfloor \frac{n}{5} \right\rfloor$。
经检验,当 时该不等式成立。
因此对于 ,直接输出所有按钮 即可。 -
小数据范围的处理
当 时,,即最终亮灯数最多为 。
我们可以枚举所有可能的最终亮灯集合(大小为 ),并反向构造按下的按钮集合。
构造方法
假设我们已经知道最终哪些灯是亮的(目标状态 是一个 位二进制掩码)。
如何找到一组按钮,使得最终状态恰好为 ?贪心构造:
从 到 依次考虑按钮 。
设当前灯的状态为 (初始为 )。如果灯 的状态 与目标 不同,则按下按钮 ,并更新所有 的倍数的灯。
因为按钮 只影响编号 的灯,且我们按顺序处理,所以这种贪心一定可以得到唯一解(每个灯的状态最终由其约数中最大的被按按钮决定)。
因此我们可以由 唯一确定要按的按钮集合 (用掩码表示)。检查约束
得到 后,需要验证:
- (至少按一个按钮)
- 对每条约束 :若 ,则必须有 。
这可以通过预处理每个按钮 的“必须按”集合 (掩码),然后检查 即可。
算法流程
- 若 :输出 ,结束。
- 预处理:
lamp_mask[i]:按钮 影响的灯的掩码( 从 到 编号)。must[i]:若按按钮 ,则必须按的所有按钮的掩码。
- 枚举所有大小 的目标亮灯子集 :
- 使用贪心构造按钮集 。
- 检查 是否非空且满足所有约束。
- 若满足,输出 中按钮编号( 转回 -based)。
- 若枚举完仍未找到,输出 。
复杂度分析
- :每个测试 。
- :枚举子集数最多$$\sum_{k=0}^{3} \binom{19}{k} = 1 + 19 + 171 + 969 = 1160.
$$每次构造和检查均为 (可用位运算优化到 左右)。
由于所有测试的 总和 ,总时间非常充裕。
代码实现(C++)
#include <bits/stdc++.h> using namespace std; void solve() { int n, m; cin >> n >> m; vector<pair<int,int>> constraints(m); for (int i = 0; i < m; ++i) { cin >> constraints[i].first >> constraints[i].second; --constraints[i].first; --constraints[i].second; } // 情况1:n >= 20 if (n >= 20) { cout << n << "\n"; for (int i = 1; i <= n; ++i) { cout << i << " \n"[i == n]; } return; } // 情况2:n <= 19 vector<int> lamp_mask(n, 0); for (int i = 0; i < n; ++i) { int step = i + 1; for (int j = i; j < n; j += step) { lamp_mask[i] |= 1 << j; } } vector<int> must(n, 0); for (auto [u, v] : constraints) { must[u] |= 1 << v; } vector<int> ans_buttons; bool found = false; auto check = [&](int T) { int cur = 0, B = 0; for (int i = 0; i < n; ++i) { if (((cur >> i) & 1) != ((T >> i) & 1)) { B |= 1 << i; cur ^= lamp_mask[i]; } } if (B == 0) return false; for (int i = 0; i < n; ++i) { if ((B >> i) & 1) { if ((B & must[i]) != must[i]) return false; } } for (int i = 0; i < n; ++i) { if ((B >> i) & 1) ans_buttons.push_back(i + 1); } return true; }; // 枚举大小为 0..3 的子集 if (check(0)) found = true; for (int i = 0; i < n && !found; ++i) { if (check(1 << i)) found = true; } for (int i = 0; i < n && !found; ++i) { for (int j = i + 1; j < n && !found; ++j) { if (check((1 << i) | (1 << j))) found = true; } } for (int i = 0; i < n && !found; ++i) { for (int j = i + 1; j < n && !found; ++j) { for (int k = j + 1; k < n && !found; ++k) { if (check((1 << i) | (1 << j) | (1 << k))) found = true; } } } if (!found) { cout << "-1\n"; } else { cout << ans_buttons.size() << "\n"; for (size_t i = 0; i < ans_buttons.size(); ++i) { cout << ans_buttons[i] << " \n"[i + 1 == ans_buttons.size()]; } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) solve(); return 0; }总结
- 利用完全平方数的性质, 时直接全按。
- 小范围暴力枚举所有可能的最终亮灯状态(最多 盏),贪心反推按钮集并验证约束。
- 时间复杂度 ,实际运行极快。
-
- 1
信息
- ID
- 6421
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者