$x=y^2-z^2$ 等价于 $x$ 有同奇偶的因子对,即 $x$ 为奇数或 $4$ 的倍数;用前缀计数 $F(R)-F(L-1)$ 即可 $O(1)$ 求出答案。
OJ: luogu
题目 ID: P9231
难度:普及-
标签:数学数论思维计数前缀和
创建: 2026-10-02 15:07
更新: 2026-10-03 10:37
形式化题目
给定两个整数
题面中的
样例
| 表示方式 | |
|---|---|
暴力解法
思路
直接从平方差的形式出发不好枚举:
记
于是「
存在两个因子
,满足 ,且 是偶数(也就是 同奇偶),并且解出的 是整数。
这就是暴力解的全部逻辑:对区间里的每个
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-03 10:37
* update_at: 2026-10-03 10:37
*/
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
// 做法:把 x = y^2 - z^2 看成 (y-z)(y+z),令 a = y-z >= 1、b = y+z >= a,
// 则 a*b = x 且 a、b 必须同奇偶。反过来,只要找到同奇偶的因子对
// 就能还原 y = (a+b)/2、z = (b-a)/2,所以逐个 x 枚举 a <= sqrt(x) 即可判断。
// 复杂度 O((R-L+1) * sqrt(R)),只适合小范围数据,不能当作正解。
#include <iostream>
using namespace std;
typedef long long ll;
// 判断 x 能否写成两个整数平方的差(y,z 取非负整数即可覆盖所有情况)。
bool can_represent(ll x) {
if (x == 0) {
return true; // 取 y = z 即可
}
// 枚举较小的因子 a,b = x / a 一定满足 b >= a。
for (ll a = 1; a * a <= x; a++) {
if (x % a != 0) {
continue;
}
ll b = x / a;
if ((a + b) % 2 == 0) {
return true; // a、b 同奇偶,可以还原出整数 y、z
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll L, R;
cin >> L >> R;
ll ans = 0;
// 朴素做法:区间里每个数都单独判断一次。
for (ll x = L; x <= R; x++) {
if (can_represent(x)) {
ans++;
}
}
cout << ans << '\n';
return 0;
}复杂度
逐个判断
瓶颈
暴力解本身完全正确,但它有两个致命问题:
可以到 ,光是区间长度就可能达到 ,逐个数枚举已经不可能。 - 即使只处理一个数,
也意味着约 次循环,乘上区间长度后彻底超时。
所以要拿满分,必须找出「哪些数可行」的充要条件,然后不再逐个判断。
正解
思路
回到暴力的判据:
设有因子对
- 都是奇数:奇数乘奇数还是奇数,所以
是奇数; - 都是偶数:写
,则 ,所以 是 的倍数。
反过来看,两种情形都真的能构造出来:
是奇数时,取 ,两个因子都是奇数, 是整数。例如 。 是 的倍数时, 是偶数,取 ,两个因子都是偶数, 是整数。例如 , 。
至于一个奇数一个偶数的情况,
于是得到本题最关键的一句话:
可以表示为两个整数平方之差,当且仅当 是奇数或者 的倍数。
用模运算写就是
接下来只是计数问题。由于「奇数」和「
答案就是前缀相减:
例如
边界上注意
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-03 10:37
* update_at: 2026-10-03 10:37
*/
// P9231 [蓝桥杯 2023 省 A] 平方差
// 核心结论:x 能写成 x = y^2 - z^2 当且仅当 x 是奇数或者 4 的倍数。
// 于是答案 = [1,R] 中这类数的个数 减去 [1,L-1] 中这类数的个数。
#include <iostream>
using namespace std;
typedef long long ll;
// 统计 [1,n] 中“奇数或 4 的倍数”的个数。
// 一个数不可能同时是奇数和 4 的倍数,所以两类直接相加,不会重复计算。
ll prefix_count(ll n) {
if (n <= 0) {
return 0;
}
ll odd = (n + 1) / 2; // 1,3,5,... 的个数
ll mul4 = n / 4; // 4,8,12,... 的个数
return odd + mul4;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll L, R;
cin >> L >> R;
// 前缀和思想:区间答案 = F(R) - F(L-1)。
cout << prefix_count(R) - prefix_count(L - 1) << '\n';
return 0;
}复杂度
每个
由于 int 其实也不会溢出;但习惯上仍然使用 long long,避免以后改动代码时踩坑。
总结
这道题的推导链条非常短,值得完整记住:
- 用平方差公式分解:
; - 于是
可行等价于「存在同奇偶的因子对」,即 是奇数或 的倍数; - 奇数与
的倍数互不重叠,用前缀计数 一次算出答案。
最容易踩的坑是把条件记成「偶数都行」:真正的偶数是