魔法序列 - 题解
标签与难度
标签: 数学, 规律探索, 数列, 前缀和, O(1)算法 难度: 1300
题目大意喵~
一位叫做伊娜的可爱女孩定义了一个神奇的“魔法序列” a,它的规则是这样的:
- 序列的第一项 等于 。
- 从第二项开始,每一项 的值,等于它前一项 在它自己()之前出现过的总次数。
用数学语言来描述就是:
其中 [...] 是艾弗森括号,如果里面的条件成立,值为1,否则为0。
我们的任务是,给定一个正整数 ,计算这个魔法序列的前 项之和,也就是 的说。
解题思路分析
喵哈~!这个序列的定义看起来像是在自己引用自己,有点绕呢。不过别担心,这种时候最好的办法就是伸出爪子,我们一起来算算序列的前几项,看看能不能发现什么小秘密,喵~
: 题目直接告诉我们了,就是 。 序列:
1: 它的值是 在它之前(也就是在第1项)出现的次数。数字
1只出现过一次,所以 。 序列:1, 1: 它的值是 在它之前(第1、2项)出现的次数。数字
1出现过两次,所以 。 序列:1, 1, 2: 它的值是 在它之前(第1、2、3项)出现的次数。数字
2只出现过一次,所以 。 序列:1, 1, 2, 1: 它的值是 在它之前(前4项)出现的次数。数字
1出现过三次(在第1, 2, 4项),所以 。 序列:1, 1, 2, 1, 3: 它的值是 在它之前(前5项)出现的次数。数字
3只出现过一次,所以 。 序列:1, 1, 2, 1, 3, 1
再多写几项:1, 1, 2, 1, 3, 1, 4, 1, 5, 1, ...
哇!一个非常有趣的规律出现了,不是吗?
- 所有偶数位置上的数()好像全都是 1 呐!
- 所有奇数位置上的数()则构成了
1, 2, 3, 4, ...这样的自然数序列!
我们可以大胆地做出猜想:
- 当 是偶数时,。
- 当 是奇数时,。
这个猜想对不对呢?我们可以用数学归纳法来证明,这里本喵就简单地解释一下为什么这个规律是成立的:
- 如果 i 是奇数(比如 ):那么 就是偶数。根据我们的猜想, 应该是 。所以 就是要计算 在前 项中出现的次数。 会出现在第1项和所有偶数项。在 的范围内,有 个奇数项(就是第1项)和 个偶数项的值是 。加起来就是 。猜想成立!
- 如果 i 是偶数(比如 ):那么 就是奇数。根据我们的猜想, 应该是 。我们要计算 在前 项中出现的次数。根据猜想,只有奇数位置的项才可能大于1,偶数位置的项都是1。所以我们只要在奇数位置找。一个奇数位置 的值是 。要让它等于 ,就需要 ,也就是 。这意味着,值 在前 项里只在第 项出现过唯一一次。所以 。猜想也成立!
既然规律找到了,那问题就变得超级简单啦!我们要求前 项和,只需要把奇数项的和跟偶数项的和加起来就可以啦。
1. 偶数项的和: 在 到 之间,一共有 个偶数。每一项的值都是 ,所以偶数项的总和就是 。
2. 奇数项的和: 在 到 之间,一共有 个奇数。我们把这些奇数项单独拿出来,它们是 。它们的值分别是 。 所以,奇数项的和就是一个从 开始的等差数列的和。如果一共有 个奇数项,它们的和就是 。
最终公式: 把两部分加起来,总和就是:
在 C++ 里做整数运算时:
- 就是
n / 2。 - 就是
(n + 1) / 2。
因为 最大可以到 ,所以计算过程中可能会超过普通 int 的范围,我们需要用 long long 来存储 和计算结果哦~
代码实现
下面是本喵根据上面的思路,精心为你准备的代码,喵~
#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;
}复杂度分析
时间复杂度: 我们的代码只是根据输入的 做了一些固定的算术运算,没有循环或者递归,所以计算时间是常数级别的,超级快!
空间复杂度: 我们只用了几个变量(
n,num_even_terms,num_odd_terms等)来存储中间值,需要的额外空间不随 的大小变化,所以是常数空间复杂度,喵~
知识点总结
这道题虽然披着“魔法”的外衣,但核心是考察我们发现规律和数学计算的能力,呐。
- 规律探索: 解决序列问题的第一步往往是手算出前几项,观察并大胆猜想规律。这是算法竞赛中非常重要的直觉和能力。
- 数学归纳法: 这是严谨证明我们猜想的有力工具,能确保我们的规律在所有情况下都成立。
- 分治思想 (按奇偶性划分): 将一个复杂的问题(求总和)分解成几个更简单的子问题(求奇数项和与偶数项和)是常用的解题策略。
- 等差数列求和: 是一个必须牢记于心的基础数学公式。
- 数据类型选择: 注意题目给定的数据范围!看到 就要立刻想到
long long,这是避免“大数溢出”问题的关键哦。
希望这篇题解能帮到你,加油,你一定可以的,喵~!