喵哈~!各位master,今天由我来给大家讲解一道关于一元二次方程的数学题,nya~。这道题看起来有点复杂,但只要我们一步步拆解,就像小猫玩毛线球一样,总能找到线头的说!
E. Square Equation Roots
题目大意 (Problem Summary)
有一个可爱的小朋友 Petya 正在研究一元二次方程,方程的形式是 x² + 2bx + c = 0
,其中 b
和 c
都是正整数。
Petya 对所有满足 1 ≤ b ≤ n
和 1 ≤ c ≤ m
的方程都非常感兴趣。他想知道,把这些方程所有的实数根都收集起来,放到一个集合里,去重之后,总共有多少个不同的实数根呢?
简单来说,就是要求所有方程 x² + 2bx + c = 0
(其中 1 ≤ b ≤ n
, 1 ≤ c ≤ m
) 的不同实数根的总数,喵~。
解题思路 (Solution Approach)
这道题的核心是“计数”和“去重”。一个很自然的想法是先算出所有根(包括重复的),然后再想办法把重复的部分去掉。这不就是伟大的容斥原理嘛!
最终答案 = (所有根的总数) - (被重复计算的根的总次数) + (所有不同种类的重复根的数量)
听起来是不是有点绕?让本喵给你解释一下~ 假设根 -1
出现了 3 次,根 -5
出现了 2 次。
- 所有根的总数:把这 3 个
-1
和 2 个-5
都算上,总共 5 个。 - 被重复计算的根的总次数:根
-1
出现了 3 次,重复了3-1=2
次;根-5
出现了 2 次,重复了2-1=1
次。所以总共重复了2+1=3
次。 - 所有不同种类的重复根的数量:这里有两种重复根,
-1
和-5
,所以是 2。 - 最终答案:
5 - 3 + 2 = 4
?不对不对,公式应该是这样的: 最终答案 = (所有根的总数) - (所有重复根的出现次数之和) + (不同重复根的种类数)5 - (3+2) + 2 = 2
。对啦!就是-1
和-5
这两种。
所以,我们的任务就分成了三步,喵~
第一步:计算所有根的总数 (Total Roots)
首先,我们来解这个方程 x² + 2bx + c = 0
。根据求根公式,根为: x = (-2b ± sqrt(4b² - 4c)) / 2 = -b ± sqrt(b² - c)
要有实数根,判别式 Δ' = b² - c
必须大于等于 0,也就是 c ≤ b²
。
对于一个固定的 b
(1 ≤ b ≤ n
):
c
的取值范围是1 ≤ c ≤ m
,同时要满足c ≤ b²
。所以c
的有效范围是1 ≤ c ≤ min(m, b²)
。- 当
c = b²
时,Δ' = 0
,方程有一个实数根x = -b
。 - 当
c < b²
时,Δ' > 0
,方程有两个不同的实数根x = -b ± sqrt(b² - c)
。
所以,对于一个固定的 b
,它能产生的根的数量是:
- 有
min(m, b²)
个有效的c
值。 - 其中一个值
c = b²
(如果b² ≤ m
) 产生 1 个根。 - 另外
min(m, b²) - 1
个c
值产生 2 个根。 - 总根数 =
1 * 1 + 2 * (min(m, b²) - 1) = 2 * min(m, b²) - 1
(这个公式在b² > m
时不成立)。
我们换个思路,更简单一点喵~:
- 对于固定的
b
,如果b² > m
,那么所有c
(1
到m
) 都满足c < b²
。所以有m
个方程,每个方程产生 2 个根,总共2 * m
个根。 - 如果
b² ≤ m
,那么c
的取值范围是1
到b²
。其中c = b²
产生 1 个根,c
从1
到b² - 1
(共b²-1
个值) 产生2 * (b² - 1)
个根。总共1 + 2 * (b² - 1) = 2b² - 1
个根。
所以,所有根的总数 total_roots
就是把所有 b
的情况加起来: total_roots = Σ (当b²≤m时, 2b²-1) + Σ (当b²>m时, 2m)
我们可以找到一个临界点 t = floor(sqrt(m))
。
- 当
b ≤ t
时,b² ≤ m
。 - 当
b > t
时,b² > m
。 所以,我们把b
从1
到n
的求和分成两部分:1
到min(n, t)
和min(n, t)+1
到n
。这样就可以高效地算出总根数啦!
第二步:找出哪些根会重复 (Finding Duplicates)
这是最关键的一步!什么时候两个不同的方程 (b₁, c₁)
和 (b₂, c₂)
会产生相同的根呢?
根的形式是 -b ± sqrt(b² - c)
。 令 d = b² - c
,根就是 -b ± sqrt(d)
。
如果根是无理数: 比如
x = -b₁ ± sqrt(d₁)
,其中d₁
不是完全平方数。 如果它和另一个根-b₂ ± sqrt(d₂)
相等,由于有理部分和无理部分必须分别相等(这是一个很重要的数论知识点哦!),那么必然有b₁ = b₂
,进而d₁ = d₂
,从而c₁ = c₂
。也就是说,无理根是唯一的,它只属于一个(b, c)
对,不会被重复计算!如果根是整数: 啊哈!只有整数根才可能被不同的
(b, c)
对产生。 假设有一个整数根x = -k
(其中k > 0
,因为b, c > 0
,根一定是负数)。 把它代入方程:(-k)² + 2b(-k) + c = 0
k² - 2bk + c = 0
c = 2bk - k²
对于一个整数根
-k
,只要我们能找到一个b
(1 ≤ b ≤ n
),使得c = 2bk - k²
满足1 ≤ c ≤ m
,那么这个(b, c)
对就能产生整数根-k
。所以,我们要做的就是:
- 遍历所有可能的整数根
-k
。 - 对于每个
-k
,计算有多少个b
能够满足1 ≤ 2bk - k² ≤ m
。 - 这个
b
的数量,就是根-k
的出现次数N_k
。
从不等式
1 ≤ 2bk - k² ≤ m
,我们可以解出b
的范围:b ≥ (k² + 1) / (2k)
b ≤ (k² + m) / (2k)
再结合1 ≤ b ≤ n
,我们就能确定b
的所有可能取值,从而算出N_k
。- 遍历所有可能的整数根
第三步:容斥原理大显神威 (Inclusion-Exclusion)
现在我们有了所有需要的东西啦:
total_roots
:所有根的总数(计重)。total_occ
:所有整数根的出现次数总和,即Σ N_k
。D_count
:不同整数根的种类数,即N_k > 0
的k
的数量。
根据我们一开始的容斥公式: 最终答案 = total_roots
- total_occ
+ D_count
举个栗子:total_roots
计算了 -1
三次,-5
两次。 total_occ
就是 3 + 2 = 5
。 D_count
就是 2
。 total_roots - 5 + 2
相当于从总数里,把 -1
的贡献减去 3
再加回 1
,把 -5
的贡献减去 2
再加回 1
。正好每个都只算了一次!完美,喵~
题解代码 (Solution Code)
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int main() {
// 使用C++标准IO加速,让程序跑得更快,喵~
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
long long n, m;
cin >> n >> m;
// 步骤一:计算所有根的总数 (total_roots)
// t_val 是 sqrt(m) 的整数部分,是 b² ≤ m 和 b² > m 的分界线
long long t_val = sqrt(m);
// 稍微修正一下 t_val,确保 (t_val+1)² > m
if ((t_val + 1) * (t_val + 1) <= m) {
t_val++;
}
long long total_roots = 0;
// b 的范围是 1 到 n,t_val 是临界点,所以我们先处理到 min(n, t_val)
long long lim = min(n, t_val);
// 对于 b ≤ t_val, b² ≤ m, 每个 b 贡献 2*b²-1 个根
for (long long b_val = 1; b_val <= lim; b_val++) {
total_roots += 2 * b_val * b_val - 1;
}
// 对于 b > t_val, b² > m, 每个 b 贡献 2*m 个根
if (n > t_val) {
total_roots += 2 * m * (n - t_val);
}
// 步骤二和三:计算整数根的重复情况
long long total_occ = 0; // 所有整数根的出现次数之和
long long D_count = 0; // 不同整数根的种类数
// 遍历所有可能的整数根 -k。k 的上限为什么是 2n-1?
// 因为 c = 2bk - k² ≥ 1, b ≤ n => 2nk > k² => k < 2n。
for (long long k = 1; k <= 2 * n; k++) {
// b 的下界是 ceil((k²+1)/(2k))
long long b_min = (k * k + 1 + 2 * k - 1) / (2 * k);
// b 的上界是 floor((k²+m)/(2k))
long long b_max = (k * k + m) / (2 * k);
// b 的实际范围要和 [1, n] 取交集
long long L = max(1LL, b_min);
long long R = min(n, b_max);
long long occ = 0; // 根 -k 的出现次数
if (R >= L) {
occ = R - L + 1;
}
if (occ > 0) {
total_occ += occ; // 累加到总出现次数
D_count++; // 发现一种新的重复根
}
}
// 最终答案 = 总根数 - 重复根总次数 + 重复根种类数
long long distinct_roots = total_roots - total_occ + D_count;
cout << distinct_roots << endl;
return 0;
}
注意: 提供的原始题解代码在计算 occ
时分成了 count1
, count2
, overlap
三部分,逻辑稍微复杂一些,但本质上也是在计算满足条件的 b
的数量。我在这里给出的简化版代码,其核心思想是相同的,都是通过 c = 2bk - k²
来找到整数根的出现次数,更容易理解一些,喵~。两种写法都能得到正确答案。
相关知识点 (Knowledge Points)
一元二次方程 (Quadratic Equation)
- 求根公式:
ax² + bx + c = 0
的根是x = [-b ± sqrt(b² - 4ac)] / (2a)
。 - 判别式:
Δ = b² - 4ac
。Δ > 0
有两个不同实根,Δ = 0
有一个实根,Δ < 0
无实根。本题中方程为x² + 2bx + c = 0
,判别式可以简化为Δ' = b² - c
。
- 求根公式:
容斥原理 (Principle of Inclusion-Exclusion)
- 一个非常重要的组合数学原理。在本题中,我们用了它最简单的形式来处理去重问题:
不重复的元素数 = 总元素数 - (重复的部分) + (被重复减去的部分)
。
- 一个非常重要的组合数学原理。在本题中,我们用了它最简单的形式来处理去重问题:
数论知识 (Number Theory)
- 有理根定理: 对于整系数多项式,其有理根
p/q
(最简分数) 必然满足p
整除常数项,q
整除最高次项系数。在本题中,x² + 2bx + c = 0
的有理根必须是整数。 - 无理数性质: 形如
a + b*sqrt(k)
(其中a, b
是有理数,sqrt(k)
是无理数) 的数,若a₁ + b₁*sqrt(k) = a₂ + b₂*sqrt(k)
,则必有a₁ = a₂
和b₁ = b₂
。这个性质帮助我们断定,无理根不会在不同的b
值之间重复。
- 有理根定理: 对于整系数多项式,其有理根
向上取整 (Ceiling Division)
- 在 C++ 中计算
ceil(a/b)
(对于正整数a, b
),一个常用技巧是(a + b - 1) / b
。这可以避免使用浮点数,保证精度。
- 在 C++ 中计算
好啦,这次的题解就到这里啦!希望 master 们都能理解。如果还有不懂的地方,随时可以再来问我哦,喵~ (ฅ'ω'ฅ)