题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(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\) 为第二关键字升序排列。

  2. 算法选择

    • 筛法标记双平方数:用布尔数组 \(f\) 标记所有 \(p^2 + q^2\)\(0 \le p, q \le m\)
    • 提取有序集合:将所有双平方数按升序存入数组 \(num\)
    • 枚举公差和首项:外层枚举公差 \(b\),中层枚举首项 \(a\),内层验证连续 \(n\) 项是否均为双平方数
  3. 关键步骤

    • 读入\(n\)(数列长度)、\(m\)\(p, q\) 上界)
    • 生成双平方数
      • 双重循环枚举 \(p, q \in [0, m]\),标记 \(f[p^2 + q^2] = 1\)
    • 提取有序数组
      • 遍历 \(i\)\(0\)\(N\),若 \(f[i] = 1\)num[++cur] = i
    • 枚举等差数列
      • 外层 \(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
    • 输出:若 flag == false 输出 NONE
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m^2 + \text{max\_num} \cdot |S| \cdot n)\),筛法 \(O(m^2)\),枚举验证部分取决于双平方数密度
    • 空间复杂度:\(O(N)\)\(N = 2m^2 + 5\),布尔标记数组和存储数组
  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
posted @ 2026-08-05 15:44  团爸讲算法  阅读(7)  评论(0)    收藏  举报