Skip to content

魔法序列 - 题解

标签与难度

标签: 数学, 规律探索, 数列, 前缀和, O(1)算法 难度: 1300

题目大意喵~

一位叫做伊娜的可爱女孩定义了一个神奇的“魔法序列” a,它的规则是这样的:

  1. 序列的第一项 a1a_1 等于 11
  2. 从第二项开始,每一项 aia_i 的值,等于它前一项 ai1a_{i-1} 在它自己(aia_i)之前出现过的总次数。

用数学语言来描述就是:

{a1=1ai=j<i[aj=ai1]for i>1\begin{cases} a_1 = 1 \\ a_i = \sum_{j<i} [a_j = a_{i-1}] & \text{for } i > 1 \end{cases}

其中 [...] 是艾弗森括号,如果里面的条件成立,值为1,否则为0。

我们的任务是,给定一个正整数 nn,计算这个魔法序列的前 nn 项之和,也就是 i=1nai\sum_{i=1}^n a_i 的说。

解题思路分析

喵哈~!这个序列的定义看起来像是在自己引用自己,有点绕呢。不过别担心,这种时候最好的办法就是伸出爪子,我们一起来算算序列的前几项,看看能不能发现什么小秘密,喵~

  1. a1a_1: 题目直接告诉我们了,就是 11。 序列: 1

  2. a2a_2: 它的值是 a21=a1=1a_{2-1} = a_1 = 1 在它之前(也就是在第1项)出现的次数。数字 1 只出现过一次,所以 a2=1a_2 = 1。 序列: 1, 1

  3. a3a_3: 它的值是 a31=a2=1a_{3-1} = a_2 = 1 在它之前(第1、2项)出现的次数。数字 1 出现过两次,所以 a3=2a_3 = 2。 序列: 1, 1, 2

  4. a4a_4: 它的值是 a41=a3=2a_{4-1} = a_3 = 2 在它之前(第1、2、3项)出现的次数。数字 2 只出现过一次,所以 a4=1a_4 = 1。 序列: 1, 1, 2, 1

  5. a5a_5: 它的值是 a51=a4=1a_{5-1} = a_4 = 1 在它之前(前4项)出现的次数。数字 1 出现过三次(在第1, 2, 4项),所以 a5=3a_5 = 3。 序列: 1, 1, 2, 1, 3

  6. a6a_6: 它的值是 a61=a5=3a_{6-1} = a_5 = 3 在它之前(前5项)出现的次数。数字 3 只出现过一次,所以 a6=1a_6 = 1。 序列: 1, 1, 2, 1, 3, 1

再多写几项:1, 1, 2, 1, 3, 1, 4, 1, 5, 1, ...

哇!一个非常有趣的规律出现了,不是吗?

  • 所有偶数位置上的数(a2,a4,a6,...a_2, a_4, a_6, ...)好像全都是 1 呐!
  • 所有奇数位置上的数(a1,a3,a5,...a_1, a_3, a_5, ...)则构成了 1, 2, 3, 4, ... 这样的自然数序列!

我们可以大胆地做出猜想:

  • ii 是偶数时,ai=1a_i = 1
  • ii 是奇数时,ai=i+12a_i = \frac{i+1}{2}

这个猜想对不对呢?我们可以用数学归纳法来证明,这里本喵就简单地解释一下为什么这个规律是成立的:

  • 如果 i 是奇数(比如 i=5i=5):那么 i1i-1 就是偶数。根据我们的猜想,ai1a_{i-1} 应该是 11。所以 aia_i 就是要计算 11 在前 i1i-1 项中出现的次数。11 会出现在第1项和所有偶数项。在 i1i-1 的范围内,有 11 个奇数项(就是第1项)和 i12\frac{i-1}{2} 个偶数项的值是 11。加起来就是 1+i12=i+121 + \frac{i-1}{2} = \frac{i+1}{2}。猜想成立!
  • 如果 i 是偶数(比如 i=6i=6):那么 i1i-1 就是奇数。根据我们的猜想,ai1a_{i-1} 应该是 (i1)+12=i2\frac{(i-1)+1}{2} = \frac{i}{2}。我们要计算 i2\frac{i}{2} 在前 i1i-1 项中出现的次数。根据猜想,只有奇数位置的项才可能大于1,偶数位置的项都是1。所以我们只要在奇数位置找。一个奇数位置 jj 的值是 j+12\frac{j+1}{2}。要让它等于 i2\frac{i}{2},就需要 j+1=ij+1=i,也就是 j=i1j=i-1。这意味着,值 i2\frac{i}{2} 在前 i1i-1 项里只在第 i1i-1 项出现过唯一一次。所以 ai=1a_i = 1。猜想也成立!

既然规律找到了,那问题就变得超级简单啦!我们要求前 nn 项和,只需要把奇数项的和跟偶数项的和加起来就可以啦。

1. 偶数项的和:11nn 之间,一共有 n/2\lfloor n/2 \rfloor 个偶数。每一项的值都是 11,所以偶数项的总和就是 n/2\lfloor n/2 \rfloor

2. 奇数项的和:11nn 之间,一共有 n/2\lceil n/2 \rceil 个奇数。我们把这些奇数项单独拿出来,它们是 a1,a3,a5,a_1, a_3, a_5, \dots。它们的值分别是 1,2,3,1, 2, 3, \dots。 所以,奇数项的和就是一个从 11 开始的等差数列的和。如果一共有 m=n/2m = \lceil n/2 \rceil 个奇数项,它们的和就是 j=1mj=m(m+1)2\sum_{j=1}^{m} j = \frac{m(m+1)}{2}

最终公式: 把两部分加起来,总和就是:

Sn=n/2+n/2(n/2+1)2S_n = \lfloor n/2 \rfloor + \frac{\lceil n/2 \rceil \cdot (\lceil n/2 \rceil + 1)}{2}

在 C++ 里做整数运算时:

  • n/2\lfloor n/2 \rfloor 就是 n / 2
  • n/2\lceil n/2 \rceil 就是 (n + 1) / 2

因为 nn 最大可以到 101810^{18},所以计算过程中可能会超过普通 int 的范围,我们需要用 long long 来存储 nn 和计算结果哦~

代码实现

下面是本喵根据上面的思路,精心为你准备的代码,喵~

cpp
#include <iostream>

int main() {
    // 为了防止终端输入输出过慢,可以加上这两句喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    // 题目中的 n 很大,所以要用 long long 来存储,不然会溢出的说
    long long n;
    std::cin >> n;

    // 计算 1 到 n 中有多少个偶数项
    // 在整数除法里,n / 2 就等于 floor(n / 2)
    long long num_even_terms = n / 2;

    // 计算 1 到 n 中有多少个奇数项
    // 对于正整数 n,(n + 1) / 2 就等于 ceil(n / 2)
    long long num_odd_terms = (n + 1) / 2;

    // 偶数项的值都是 1,所以它们的和就是偶数项的个数
    long long sum_even = num_even_terms;

    // 奇数项的值构成了 1, 2, 3, ..., num_odd_terms 的序列
    // 它们的和是等差数列求和公式:m * (m + 1) / 2
    long long sum_odd = num_odd_terms * (num_odd_terms + 1) / 2;
    
    // 总和就是两部分之和
    long long total_sum = sum_odd + sum_even;

    std::cout << total_sum << std::endl;

    return 0;
}

复杂度分析

  • 时间复杂度: O(1)O(1) 我们的代码只是根据输入的 nn 做了一些固定的算术运算,没有循环或者递归,所以计算时间是常数级别的,超级快!

  • 空间复杂度: O(1)O(1) 我们只用了几个变量(n, num_even_terms, num_odd_terms 等)来存储中间值,需要的额外空间不随 nn 的大小变化,所以是常数空间复杂度,喵~

知识点总结

这道题虽然披着“魔法”的外衣,但核心是考察我们发现规律和数学计算的能力,呐。

  1. 规律探索: 解决序列问题的第一步往往是手算出前几项,观察并大胆猜想规律。这是算法竞赛中非常重要的直觉和能力。
  2. 数学归纳法: 这是严谨证明我们猜想的有力工具,能确保我们的规律在所有情况下都成立。
  3. 分治思想 (按奇偶性划分): 将一个复杂的问题(求总和)分解成几个更简单的子问题(求奇数项和与偶数项和)是常用的解题策略。
  4. 等差数列求和: i=1mi=m(m+1)2\sum_{i=1}^m i = \frac{m(m+1)}{2} 是一个必须牢记于心的基础数学公式。
  5. 数据类型选择: 注意题目给定的数据范围!看到 101810^{18} 就要立刻想到 long long,这是避免“大数溢出”问题的关键哦。

希望这篇题解能帮到你,加油,你一定可以的,喵~!