题解:洛谷 P1214 等差数列
【题目来源】
洛谷:P1214 [USACO1.4] 等差数列 Arithmetic Progressions
【题目描述】
一个等差数列是一个能表示成 \(a, a+b, a+2b, \dots ,a+nb\space (n \in \mathbb N)\) 的数列。
在这个问题中 \(a\) 是一个非负的整数,\(b\) 是正整数。
写一个程序来找出在双平方数集合:
\[\{ x | x = p^2 + q^2 \wedge p,q \in \mathbb N \cap [0,m]\}
\]
中长度为 \(n\) 的等差数列。
【输入】
第一行一个正整数 \(n\),表示要找的数列长度。
第二行一个非负整数 \(m\),表示 \(p,q\) 的上界。
【输出】
如果没有找到数列,输出 NONE。
如果找到了,输出一行或多行,每行由二个整数组成:\(a,b\)。
这些行应该以 \(b\) 为第一关键字,\(a\) 为第二关键字升序排序。
所求的等差数列将不会多于 \(10,000\) 个。
【输入样例】
5
7
【输出样例】
1 4
37 4
2 8
29 8
1 12
5 12
13 12
17 12
5 20
2 24
【核心思想】
-
问题分析:给定 \(n\) 和 \(m\),在双平方数集合 \(S = \{p^2 + q^2 \mid 0 \le p, q \le m\}\) 中找出所有长度为 \(n\) 的等差数列 \(a, a+b, a+2b, \ldots, a+(n-1)b\)。要求输出按 \(b\) 为第一关键字、\(a\) 为第二关键字升序排列。
-
算法选择:
- 筛法标记双平方数:用布尔数组 \(f\) 标记所有 \(p^2 + q^2\)(\(0 \le p, q \le m\))
- 提取有序集合:将所有双平方数按升序存入数组 \(num\)
- 枚举公差和首项:外层枚举公差 \(b\),中层枚举首项 \(a\),内层验证连续 \(n\) 项是否均为双平方数
-
关键步骤:
- 读入:\(n\)(数列长度)、\(m\)(\(p, q\) 上界)
- 生成双平方数:
- 双重循环枚举 \(p, q \in [0, m]\),标记 \(f[p^2 + q^2] = 1\)
- 提取有序数组:
- 遍历 \(i\) 从 \(0\) 到 \(N\),若 \(f[i] = 1\) 则
num[++cur] = i
- 遍历 \(i\) 从 \(0\) 到 \(N\),若 \(f[i] = 1\) 则
- 枚举等差数列:
- 外层 \(b\) 从 \(1\) 到
num[cur](最大双平方数) - 中层 \(a\) 从
num[1]到num[cur-1]:- 剪枝:若 \(a + (n-1) \cdot b > \text{max\_num}\),后续更大的 \(a\) 也不可能,直接
break - 内层验证 \(k\) 从 \(2\) 到 \(n\):检查 \(a + (k-1) \cdot b\) 是否为双平方数
- 若全部 \(n\) 项均为双平方数:输出 \(a\) 和 \(b\),标记
flag = true
- 剪枝:若 \(a + (n-1) \cdot b > \text{max\_num}\),后续更大的 \(a\) 也不可能,直接
- 外层 \(b\) 从 \(1\) 到
- 输出:若
flag == false输出NONE
-
时间/空间复杂度:
- 时间复杂度:\(O(m^2 + \text{max\_num} \cdot |S| \cdot n)\),筛法 \(O(m^2)\),枚举验证部分取决于双平方数密度
- 空间复杂度:\(O(N)\),\(N = 2m^2 + 5\),布尔标记数组和存储数组
-
枚举验证的核心思想:
- 筛法预处理:将"是否为双平方数"的判定预处理为 \(O(1)\) 查询,避免每次计算平方和
- 有序集合枚举:提取有序数组后,按 \(b\) 和 \(a\) 的顺序自然满足输出排序要求
- 剪枝优化:利用 \(a + (n-1)b \le \text{max\_num}\) 提前终止不可能的首项枚举
- 验证而非构造:不直接构造等差数列,而是枚举参数后验证每项的 membership
- 适用于数论集合查询、等差数列枚举、筛法预处理类问题
【算法标签】
普及- #数学
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 250 * 250 * 2 + 5; // 定义数组最大容量:m最大250,p²+q²最大为250²+250²=125000
int n, m; // n为要找的等差数列长度,m为p和q的上界
int f[N], cur, k; // f[i]标记i是否为双平方数;cur记录双平方数的个数;k用于循环计数
int num[N]; // num数组存储所有双平方数(有序)
bool flag; // flag标记是否找到至少一个等差数列
int main()
{
cin >> n >> m; // 读入数列长度n和p,q的上界m
// 第一步:生成所有双平方数(p²+q²,其中0≤p,q≤m)
for (int i = 0; i <= m; i++) // 枚举p
for (int j = 0; j <= m; j++) // 枚举q
f[i * i + j * j] = 1; // 标记p²+q²为双平方数
// 第二步:将所有双平方数按升序存入num数组
for (int i = 0; i < N; i++)
if (f[i]) // 如果i是双平方数
num[++cur] = i; // 加入num数组,cur计数加1
// 第三步:枚举所有可能的等差数列
// 外层循环枚举公差i(b)
for (int i = 1; i < num[cur]; i++) // 公差i从1到最大双平方数
{
// 中层循环枚举首项num[j](a)
for (int j = 1; j < cur - 1; j++) // 首项从第一个双平方数开始
{
// 剪枝:如果首项加上(n-1)倍公差超过最大双平方数,后续更大的首项也不可能,直接退出
if (num[j] + (n - 1) * i > num[cur])
break;
// 内层循环验证从num[j]开始、公差为i的n项是否都是双平方数
for (k = 2; k <= n; k++) // 验证第2项到第n项
if (!f[num[j] + (k - 1) * i]) // 如果某项不是双平方数
break; // 验证失败,退出内层循环
// 如果k达到n+1,说明所有n项都是双平方数,找到了一个合法等差数列
if (k == n + 1 && f[num[j] + (n - 1) * i])
{
cout << num[j] << " " << i << endl; // 输出首项a和公差b
flag = true; // 标记找到了等差数列
}
}
}
// 如果没有找到任何等差数列,输出NONE
if (!flag)
cout << "NONE" << endl;
return 0;
}
【运行结果】
5
7
1 4
37 4
2 8
29 8
1 12
5 12
13 12
17 12
5 20
2 24
浙公网安备 33010602011771号