通过预处理双平方数集合与布尔查找表,枚举首项与次项确定公差,结合末项前置剪枝快速找出所有满足条件的等差数列。
OJ: roj
题目 ID: 2015
难度:普及+/提高-
标签:暴力枚举剪枝哈希/打表
创建: 2026-10-01 02:54
更新: 2026-10-07 13:50
形式化题目
给定正整数
求所有由集合
输出所有满足条件的数列对应的数对 NONE。
正解
思路
1. 双平方数集合预处理
双平方数的值域上限为 bytearray)中标记其存在性。
随后将所有为真的数值提取为一个升序排列的列表。在
2. 枚举策略选择与公差确定
一个等差数列完全由首项
- 若直接枚举公差
和首项 ,由于大部分 的前两项甚至都不在集合中,会产生大量无效判断。 - 因而更高效的策略是:直接在双平方数列表中枚举前两项。
- 外层循环枚举数列首项
(位于列表第 项); - 内层循环枚举数列第二项
(位于列表第 项, ); - 公差天然确定为
。
- 外层循环枚举数列首项
3. 剪枝策略
为了避免枚举过多元组,可进行以下几层剪枝:
- 公差上界剪枝:整个数列最大项为
。由于该项不能超过值域上界 ,公差必须满足: 由于列表是单调递增的,一旦枚举到的第二项导致超出该上限,内层循环可直接 break终止。 - 末项前置剪枝:等差数列中间项能否全部合法,末项
是最严苛的关卡。先利用布尔查找表在 时间内检查末项是否在集合中;若末项不在集合中,直接跳过当前公差,避免逐项遍历中间项。 - 中间项倒序检验:当末项合法后,从
向下倒序检查各项 是否在集合中,只要发现一项不符即立刻停止。
收集全部合法数对后,按
代码
Python 版:
python
#!/usr/bin/env python3
# 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-04-18 10:00
# update_at: 2026-04-18 10:00
import sys
def build_bisquare_table(max_m: int) -> tuple[bytearray, list[int]]:
"""生成双平方数布尔查找表与有序升序列表。"""
max_val = 2 * max_m * max_m
is_bisquare = bytearray(max_val + 1)
for p in range(max_m + 1):
p2 = p * p
for q in range(p, max_m + 1):
is_bisquare[p2 + q * q] = 1
# 列表推导提取有序双平方数
bisquares = [val for val, exists in enumerate(is_bisquare) if exists]
return is_bisquare, bisquares
def find_arithmetic_progressions(
n: int, max_m: int, is_bisquare: bytearray, bisquares: list[int]
) -> list[tuple[int, int]]:
"""搜索所有长度为 n 的双平方数等差数列 (a, b)。"""
max_val = 2 * max_m * max_m
results: list[tuple[int, int]] = []
total = len(bisquares)
for i in range(total):
first = bisquares[i]
diff_limit = (max_val - first) // (n - 1)
for j in range(i + 1, total):
diff = bisquares[j] - first
if diff > diff_limit:
break
# 先剪枝末项,再倒序检查中间项
last = first + (n - 1) * diff
if not is_bisquare[last]:
continue
is_valid = True
for step in range(n - 2, 1, -1):
if not is_bisquare[first + step * diff]:
is_valid = False
break
if is_valid:
results.append((diff, first))
results.sort()
return [(first, diff) for diff, first in results]
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
seq_len = next(data)
max_coord = next(data)
is_bisquare, bisquares = build_bisquare_table(max_coord)
ans = find_arithmetic_progressions(seq_len, max_coord, is_bisquare, bisquares)
if not ans:
print("NONE")
else:
print("\n".join(f"{first} {diff}" for first, diff in ans))
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-04 13:45
* update_at: 2026-10-04 13:45
*/
#include <cstdio>
#include <vector>
#include <algorithm>
typedef long long ll;
const int MAXV = 125005; // 双平方数上界 2 * 250^2 = 125000
int n, m;
int maxv; // 本题的双平方数上界 2*M*M
bool in_s[MAXV]; // in_s[x] = x 是否是双平方数(p^2 + q^2)
int s_list[MAXV]; // s_list[] 存所有双平方数(升序),下标从 0 开始
int cnt_s = 0; // 双平方数个数
struct Ans {
ll a;
ll b;
};
std::vector<Ans> ans; // 所有合法数对 (a, b)
// 预处理双平方数集合:枚举 0 <= p <= q <= M 标记所有 p^2 + q^2
void build_set() {
for (int p = 0; p <= m; ++p) {
for (int q = p; q <= m; ++q) {
int v = p * p + q * q;
in_s[v] = true;
}
}
// 把双平方数按升序提取进列表
for (int x = 0; x <= maxv; ++x) {
if (in_s[x]) {
s_list[cnt_s++] = x;
}
}
}
// 检查首项 a(列表下标 i)、第二项 c(列表下标 j)出发的数列是否合法
bool check(ll a, ll b) {
// 末项前置剪枝:末项 a + (n-1)*b 是最严苛的关卡,先 O(1) 检查
ll last = a + (ll)(n - 1) * b;
if (!in_s[last]) return false;
// 中间项倒序检验:从 k = n-2 向下检查,发现不符立刻停止
for (int k = n - 2; k >= 1; --k) {
if (!in_s[a + (ll)k * b]) return false;
}
return true;
}
// 按 (b, a) 升序比较
bool cmp_ans(const Ans &x, const Ans &y) {
if (x.b != y.b) return x.b < y.b;
return x.a < y.a;
}
int main() {
scanf("%d %d", &n, &m);
maxv = 2 * m * m;
build_set();
// 在双平方数列表里枚举前两项,公差 b = c - a 天然确定
for (int i = 0; i < cnt_s; ++i) {
ll a = s_list[i];
// 公差上界:末项 a + (n-1)*b <= maxv
ll b_limit = (maxv - a) / (n - 1);
for (int j = i + 1; j < cnt_s; ++j) {
ll b = (ll)s_list[j] - a; // 公差 = 第二项 - 首项,恒为正
if (b > b_limit) break; // 列表递增,b 超上界后面只会更大,直接 break
if (check(a, b)) {
Ans t;
t.a = a;
t.b = b;
ans.push_back(t);
}
}
}
if (ans.empty()) {
printf("NONE\n");
return 0;
}
sort(ans.begin(), ans.end(), cmp_ans);
for (int i = 0; i < (int)ans.size(); ++i) {
printf("%lld %lld\n", ans[i].a, ans[i].b);
}
return 0;
}复杂度
- 时间复杂度:
- 预处理双平方数集合需枚举
,时间复杂度为 ,当 时循环约 次。 - 搜索等差数列时,虽然双平方数个数
,但严格的公差上限约束 配合末项 前置剪枝,过滤掉了绝大多数无效状态。整个搜索过程在 秒内即可完成。 - 结果排序至多
个元素,耗时 ,可忽略不计。
- 预处理双平方数集合需枚举
- 空间复杂度:
- 布尔数组占用
字节(约 )。 - 双平方数列表与答案列表占用空间均为
级别,峰值内存不足 ,远低于 限制。
- 布尔数组占用
总结
本题的关键在于利用双平方数的值域范围较小(
图示解析
以样例
状态判定流程
flowchart TD
A["枚举首项 a in S"] --> B["枚举第二项 c in S (c > a)"]
B --> C["计算公差 b = c - a"]
C --> D{"b <= (2M^2 - a) / (N - 1) ?"}
D -- 否 --> E["直接 break 跳出内层循环"]
D -- 是 --> F{"末项 a + (N-1)b 是否在集合中?"}
F -- 否 --> G["continue 跳过当前分支(末项剪枝)"]
F -- 是 --> H["倒序逐项检查 a + kb 是否全在集合中"]
H --> I{"全部项均在集合中?"}
I -- 是 --> J["记录合法解 (b, a)"]
I -- 否 --> K["舍弃"]
样例搜索过程推演(部分)
对于数列长度
| 项数 |
表达式 |
数值 | 对应的 |
是否在集合中 |
|---|---|---|---|---|
| 第 1 项 ( |
是 | |||
| 第 2 项 ( |
是 | |||
| 第 3 项 ( |
是 | |||
| 第 4 项 ( |
是 | |||
| 第 5 项 ( |
是 |
全部 5 项均能在