徐老师的连续正整数和

【题目来源】

瑞学堂:徐老师的连续正整数和

【题目描述】

徐老师发现,有些正整数可以表示为一个或多个连续正整数的和。
例如:

  • \(9=4+5\)
  • \(9=2+3+4\)
  • \(15=7+8\)
  • \(15=4+5+6\)
  • \(15=1+2+3+4+5\)

现在,给定一个正整数 \(n\),请你帮助徐老师计算,有多少种不同的连续正整数序列(至少包含两个数),使得这些数的和恰好等于 \(n\)

【输入】

一行,一个正整数 \(n (1≤n≤10^5)\)

【输出】

一行,一个整数,表示和为 \(n\) 的连续正整数序列(长度至少为 \(2\))的个数。

【输入样例】

9

【输出样例】

2

【核心思想】

  1. 问题分析:给定正整数 \(n\),求有多少个长度至少为 \(2\) 的连续正整数序列,其和恰好等于 \(n\)。这是一个数学推导 + 枚举优化问题,关键在于利用等差数列求和公式将双重循环转化为单重循环,将时间复杂度从 \(O(n^2)\) 优化到 \(O(\sqrt{n})\)

  2. 算法选择

    • 数学推导(等差数列求和):设连续序列首项为 \(a\),长度为 \(i\)\(i \geq 2\)),则

      \[n = a + (a+1) + \dots + (a+i-1) = i \cdot a + \frac{i(i-1)}{2} \]

      变形得 \(n - \frac{i(i-1)}{2} = i \cdot a\),即 \(a = \frac{n - \frac{i(i-1)}{2}}{i}\)
    • 单重枚举:枚举序列长度 \(i\),判断剩余值是否能被 \(i\) 整除且首项 \(a > 0\)
  3. 关键步骤

    • 公式变形:由 \(n = i \cdot a + \frac{i(i-1)}{2}\),得 \(a = \frac{n - \frac{i(i-1)}{2}}{i}\)
    • 枚举长度 \(i\)(从 \(2\) 开始,终止条件 \(\frac{i(i-1)}{2} < n\)):
      • 计算 \(rem = n - \frac{i(i-1)}{2}\)
      • \(rem > 0\)\(rem \bmod i = 0\),则存在合法首项 \(a = \frac{rem}{i}\)ans++
    • 终止条件分析:当 \(\frac{i(i-1)}{2} \geq n\) 时,\(rem \leq 0\),首项非正,无需继续枚举
    • 输出答案 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(\sqrt{n})\),由 \(\frac{i(i-1)}{2} < n\)\(i = O(\sqrt{n})\)
    • 空间复杂度:\(O(1)\),仅使用常数变量
  5. 数学优化的核心思想

    • 消元降维:通过等差数列公式将"枚举首项 + 枚举末项"的双重循环,转化为"枚举长度 + 验证整除"的单重循环,利用代数变形消除一个维度
    • 整除约束转存在性:将"是否存在正整数首项 \(a\)"转化为"剩余值 \(rem\) 是否为 \(i\) 的正倍数",避免实际计算 \(a\)
    • 终止条件的紧界\(\frac{i(i-1)}{2} < n\) 确保 \(rem > 0\)\(a > 0\),同时限制枚举范围在 \(O(\sqrt{n})\)
    • 连续和问题的通用技巧\(n\) 表示为连续 \(i\) 个整数之和,等价于 \(n\) 存在奇因子或特定分解形式,本题通过直接枚举长度实现
    • 适用于"连续序列和"类问题,核心在于利用求和公式建立长度与首项的关系,通过整除性判断解的存在性

【算法标签】

模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long    // 将int定义为long long,避免累加和溢出
int n, ans;              // n为给定的正整数,ans记录满足条件的连续正整数序列个数

signed main()            // 使用signed main配合#define int long long
{
    cin >> n;            // 读入正整数n
    // 枚举连续序列的起点a
    for (int a = 1; a <= n; a++)  // a为连续序列的第一个数
    {
        int sum = 0;              // sum记录当前连续序列的累加和
        // 从起点a开始向后累加,枚举序列的终点b
        for (int b = a; b <= n; b++)  // b为连续序列的当前累加到的数
        {
            sum += b;             // 将b加入累加和
            if (sum > n)          // 如果累加和已经超过n
                break;            // 后续b更大,sum只会更大,直接退出内层循环
            if (sum == n && b > a)  // 如果累加和恰好等于n,且序列长度至少为2(b>a)
                ans++;            // 找到一个满足条件的连续序列,答案加1
        }
    }
    cout << ans << endl;          // 输出满足条件的连续正整数序列个数
    return 0;
}
// 时间优化的版本
#include <bits/stdc++.h>
using namespace std;
#define int long long    // 将int定义为long long,避免中间计算溢出
int n, ans;              // n为给定的正整数,ans记录满足条件的连续正整数序列个数

signed main()            // 使用signed main配合#define int long long
{
    cin >> n;            // 读入正整数n
    // 数学推导:设连续序列长度为i,首项为a
    // 则 n = a + (a+1) + ... + (a+i-1) = i*a + i*(i-1)/2
    // 即 n - i*(i-1)/2 = i*a,需要该值为正且能被i整除
    for (int i = 2; i * (i - 1) / 2 < n; i++)  // 枚举序列长度i(至少为2)
    {
        int rem = n - i * (i - 1) / 2;  // 计算去掉0+1+...+(i-1)后的剩余值
        // rem = i*a,需要rem>0(保证首项a为正整数)且rem能被i整除
        if (rem > 0 && rem % i == 0)    // 如果rem为正且能被i整除,则存在合法首项a = rem/i
            ans++;                      // 找到一个满足条件的序列,答案加1
    }
    cout << ans << endl;                // 输出满足条件的连续正整数序列个数
    return 0;
}

【运行结果】

9
2
posted @ 2026-08-06 08:44  团爸讲算法  阅读(9)  评论(0)    收藏  举报