题解:洛谷 P1214 [USACO1.4] 等差数列 Arithmetic Progressions

本文分享的必刷题目是从蓝桥云课洛谷AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。

欢迎大家订阅我的专栏:算法题解:C++与Python实现

附上汇总贴:算法竞赛备考冲刺必刷题(C++) | 汇总


【题目来源】

洛谷:P1214 [USACO1.4] 等差数列 Arithmetic Progressions

【题目描述】

一个等差数列是一个能表示成 a , a + b , a + 2 b , … , a + n b   ( n ∈ N ) a, a+b, a+2b, \dots ,a+nb\space (n \in \mathbb N) a,a+b,a+2b,,a+nb (nN) 的数列。

在这个问题中 a a a 是一个非负的整数, b b b 是正整数。
写一个程序来找出在双平方数集合:
{ x ∣ x = p 2 + q 2 ∧ p , q ∈ N ∩ [ 0 , m ] } \{ x | x = p^2 + q^2 \wedge p,q \in \mathbb N \cap [0,m]\} {xx=p2+q2p,qN[0,m]}
中长度为 n n n 的等差数列。

【输入】

第一行一个正整数 n n n,表示要找的数列长度。
第二行一个非负整数 m m m,表示 p , q p,q p,q 的上界。

【输出】

如果没有找到数列,输出 NONE

如果找到了,输出一行或多行,每行由二个整数组成: a , b a,b a,b

这些行应该以 b b b 为第一关键字, a a a 为第二关键字升序排序。

所求的等差数列将不会多于 10 , 000 10,000 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 n n m m m,在双平方数集合 S = { p 2 + q 2 ∣ 0 ≤ p , q ≤ m } S = \{p^2 + q^2 \mid 0 \le p, q \le m\} S={p2+q20p,qm} 中找出所有长度为 n n n 的等差数列 a , a + b , a + 2 b , … , a + ( n − 1 ) b a, a+b, a+2b, \ldots, a+(n-1)b a,a+b,a+2b,,a+(n1)b。要求输出按 b b b 为第一关键字、 a a a 为第二关键字升序排列。

  2. 算法选择

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

    • 读入 n n n(数列长度)、 m m m p , q p, q p,q 上界)
    • 生成双平方数
      • 双重循环枚举 p , q ∈ [ 0 , m ] p, q \in [0, m] p,q[0,m],标记 f [ p 2 + q 2 ] = 1 f[p^2 + q^2] = 1 f[p2+q2]=1
    • 提取有序数组
      • 遍历 i i i 0 0 0 N N N,若 f [ i ] = 1 f[i] = 1 f[i]=1num[++cur] = i
    • 枚举等差数列
      • 外层 b b b 1 1 1num[cur](最大双平方数)
      • 中层 a a anum[1]num[cur-1]
        • 剪枝:若 a + ( n − 1 ) ⋅ b > max_num a + (n-1) \cdot b > \text{max\_num} a+(n1)b>max_num,后续更大的 a a a 也不可能,直接 break
        • 内层验证 k k k 2 2 2 n n n:检查 a + ( k − 1 ) ⋅ b a + (k-1) \cdot b a+(k1)b 是否为双平方数
        • 若全部 n n n 项均为双平方数:输出 a a a b b b,标记 flag = true
    • 输出:若 flag == false 输出 NONE
  4. 时间/空间复杂度

    • 时间复杂度: O ( m 2 + max_num ⋅ ∣ S ∣ ⋅ n ) O(m^2 + \text{max\_num} \cdot |S| \cdot n) O(m2+max_numSn),筛法 O ( m 2 ) O(m^2) O(m2),枚举验证部分取决于双平方数密度
    • 空间复杂度: O ( N ) O(N) O(N) N = 2 m 2 + 5 N = 2m^2 + 5 N=2m2+5,布尔标记数组和存储数组
  5. 枚举验证的核心思想

    • 筛法预处理:将"是否为双平方数"的判定预处理为 O ( 1 ) O(1) O(1) 查询,避免每次计算平方和
    • 有序集合枚举:提取有序数组后,按 b b b a a a 的顺序自然满足输出排序要求
    • 剪枝优化:利用 a + ( n − 1 ) b ≤ max_num a + (n-1)b \le \text{max\_num} a+(n1)bmax_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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值