题目描述
我们定义一个自然数子集为“非幂集”,如果该子集中不存在任何子集(可以是它本身)使得其元素之和等于某个幂数。这里的幂数定义为:对于所有 N N N 和 M ≥ 2 M \geq 2 M≥2 ,形如 N M N^M NM 的数。注意, 1 1 1 不被视为幂数。
给定整数 a a a 和 b b b ,我们的目标是找出区间 [ a , b ] [a, b] [a,b] 中满足上述性质的第一个最大子集。子集 X X X 排在 Y Y Y 前面,如果 X X X 中至少存在一个元素小于或等于 Y Y Y 中的每个元素。如果第一个值相同,则输出第二个值更小的解,依此类推。一个子集被称为“最大的”,如果不能再向其中添加任何元素而不破坏性质。
输入格式
输入文件包含多个测试用例,每行一个。每个测试用例包含两个整数 a a a 和 b b b ,满足 1 ≤ a ≤ b ≤ 1000 1 \leq a \leq b \leq 1000 1≤a≤b≤1000 。输入以 EOF \texttt{EOF} EOF 结束。
输出格式
对于每个输入,输出一行,包含属于该子集的数字,按升序排列并用空格分隔。子集至少包含一个元素。
样例输入
2 3
3 20
4 28
样例输出
2 3
3 7 10 11
5 6 7 17 28
题目分析与解题思路
问题理解
我们需要在区间 [ a , b ] [a, b] [a,b] 中找到一个满足以下条件的子集:
- 非幂性:子集的任何子集(包括自身)的元素之和不能是幂数。
- 最大性:不能再向该子集中添加任何 [ a , b ] [a, b] [a,b] 中的元素而不破坏非幂性。
- 字典序最小:在所有最大子集中,选择“第一个”最大的子集,即按题目定义的偏序关系最小的子集。
题目中的偏序定义可以理解为:比较两个子集排序后的元素序列,按字典序比较,选择较小的那个。
关键观察
-
幂数定义:幂数是形如 N M N^M NM 的数,其中 N ≥ 2 N \geq 2 N≥2 , M ≥ 2 M \geq 2 M≥2 ,且 1 1 1 不是幂数。在区间 [ 1 , 1000 ] [1, 1000] [1,1000] 中,幂数包括 4 , 8 , 9 , 16 , 25 , 27 , 32 , 36 , 49 , 64 , 81 , 100 , … 4, 8, 9, 16, 25, 27, 32, 36, 49, 64, 81, 100, \ldots 4,8,9,16,25,27,32,36,49,64,81,100,… 等。
-
单个元素的限制:如果某个数本身就是幂数,那么它绝对不能出现在子集中,因为单个元素就构成了一个子集,其和就是该幂数。
-
组合限制:对于子集中的任意多个元素,它们的和不能是幂数。这意味着我们需要避免某些数字组合。
解题策略
由于题目要求的是字典序最小的极大子集,我们可以采用贪心策略:
- 按升序处理数字:从 a a a 到 b b b 依次考虑每个数字。
- 检查能否加入:对于当前数字 n n n ,检查如果将其加入当前子集,是否会与现有元素形成幂数和。
- 加入决策:如果能安全加入,则加入;否则跳过。
- 继续处理:处理下一个数字,直到区间结束。
这样得到的子集就是字典序最小的极大子集,因为:
- 我们总是优先考虑较小的数字
- 一旦能加入就加入,确保得到的子集在字典序上尽可能小
- 最终得到的子集是极大的,因为后续数字都不能再加入
检查安全性的方法
我们需要高效地检查加入数字 n n n 是否会破坏非幂性。设当前子集的所有可能的子集和为集合 S S S (包括空集和 0 0 0 )。加入数字 n n n 后,新的子集和集合将变为 S ∪ { n } ∪ { s + n ∣ s ∈ S } S \cup \{n\} \cup \{s + n \mid s \in S\} S∪{n}∪{s+n∣s∈S} 。
检查安全性的条件为:
- n n n 本身不是幂数
- 对于所有 s ∈ S s \in S s∈S , s + n s + n s+n 不是幂数
由于 b ≤ 1000 b \leq 1000 b≤1000 ,最大可能的子集和不超过 1000 × 1000 / 2 = 500500 1000 \times 1000 / 2 = 500500 1000×1000/2=500500 ,我们可以预处理所有不超过 500500 500500 500500 的幂数。
算法步骤
- 预处理幂数:生成所有不超过 500500 500500 500500 的幂数。
- 对于每个测试用例:
- 初始化当前子集和集合 S = { 0 } S = \{0\} S={0} (空集的和)
- 初始化结果集合 R = ∅ R = \emptyset R=∅
- 对于
n
n
n 从
a
a
a 到
b
b
b :
- 如果 n n n 是幂数,跳过
- 检查对于所有 s ∈ S s \in S s∈S , s + n s + n s+n 是否是幂数
- 如果没有冲突,将 n n n 加入 R R R ,并更新 S = S ∪ { n } ∪ { s + n ∣ s ∈ S } S = S \cup \{n\} \cup \{s + n \mid s \in S\} S=S∪{n}∪{s+n∣s∈S}
- 输出 R R R
复杂度分析
- 预处理幂数: O ( M log M ) O(\sqrt{M} \log M) O(MlogM) ,其中 M = 500500 M = 500500 M=500500 ,可以接受。
- 每个测试用例:
- 最多处理 1000 1000 1000 个数字
- 每次检查时, S S S 的大小最多约 1000 1000 1000 (实际上更少,因为很多和会重复)
- 总操作约 1000 × 1000 = 1 0 6 1000 \times 1000 = 10^6 1000×1000=106 次,加上集合操作,可以在时限内完成。
实现细节
- 使用
unordered_set存储当前子集和集合,实现 O ( 1 ) O(1) O(1) 的平均查找和插入。 - 使用两个
unordered_set交替更新,避免频繁复制。 - 预处理幂数数组,实现 O ( 1 ) O(1) O(1) 的幂数检查。
参考代码
// Non-Powerful Subsets
// UVa ID: 10663
// Verdict: Accepted
// Submission Date: 2025-12-15
// UVa Run Time: 0.620s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXSUM = 500600;
bool isPower[MAXSUM];
int main() {
cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
// 预处理所有幂数(N^M,N>=2,M>=2)
for (int n = 2; n * n < MAXSUM; n++) {
int v = n * n;
while (v < MAXSUM) {
isPower[v] = true;
v *= n;
}
}
int a, b;
while (cin >> a >> b) {
unordered_set<int> sums[2]; // 两个集合交替使用
vector<int> r; // 结果集合
int u = 0, v = 1; // 当前和下一个集合的索引
for (int n = a; n <= b; n++) {
if (isPower[n]) continue; // n本身是幂数,跳过
// 检查是否安全:对于所有当前和s,s+n不能是幂数
bool safe = true;
for (auto s : sums[u]) {
if (isPower[s + n]) {
safe = false;
break;
}
}
if (!safe) continue;
// 安全,加入结果
r.push_back(n);
// 更新子集和集合
sums[v].clear();
for (auto s : sums[u]) {
sums[v].insert(s);
sums[v].insert(s + n);
}
sums[v].insert(n);
// 交换当前和下一个集合
swap(u, v);
}
// 输出结果
if (!r.empty()) {
cout << r[0];
for (size_t i = 1; i < r.size(); ++i) {
cout << ' ' << r[i];
}
}
cout << '\n';
}
return 0;
}
总结
本题的关键在于理解题目要求的是字典序最小的极大子集,而不是大小最大的子集。通过贪心策略,按升序处理数字,并检查加入后是否与现有元素形成幂数和,我们可以高效地得到正确答案。使用 unordered_set 存储子集和集合,以及预处理幂数数组,是算法高效运行的关键。
该算法的时间复杂度为 O ( ( b − a + 1 ) ⋅ ∣ S ∣ ) O((b-a+1) \cdot |S|) O((b−a+1)⋅∣S∣) ,其中 ∣ S ∣ |S| ∣S∣ 是当前子集和集合的大小,对于题目范围完全可行。
6万+

被折叠的 条评论
为什么被折叠?



