1 条题解
-
0
自动搬运
来自洛谷,原作者为

aimcf
NOIP2024 rp++!搬运于
2025-08-24 22:40:13,当前版本为作者最后更新于2022-10-05 15:57:49,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
这一篇题解的目标:让初学 OI 的蒟蒻也能理解这道题,并且拿到 分的好成绩。
函数的含义
int f(int x){ int ans = 0; while (x % 2 == 0){ x /= 2; ans += 1; } return ans; }进一步分析这个函数:
这是一个循环,当 ,也就是当 是奇数的时候,停止循环并且返回计数器,否则进入循环,计数器自增 ,并且让 除以 。
蒟蒻:咦?这是什么?
那么这样解释:
将一个数 转换成二进制,求 的末尾有多少个连续的 。
如果一个数 ,那么 的二进制是 ,容易发现末尾没有 ,也就是说,。
如果一个数 ,那么 的二进制是 ,容易发现末尾有两个 。也就是说,。
由于
x /= 2这一句话可以看做x >>= 1,也就是将二进制右移一位。所以这个程序就是求解二进制的末尾有多少个连续的 的。进一步理解,
x >>= 1可以看做x /= 2,那么求二进制的末尾有多少个连续的 ,就可以看做是求 里面有多少个因子 。在这一个
Subtask里,。也就是说,我们有一些不同的方法:方法 :直接判断 和 的大小。但是有一点麻烦,并且时间复杂度较高,是 的。
方法 :考虑 函数的性质。
容易发现,如果 是一个奇数,那么 。
原因:奇数没有因子 。
同理,偶数有因子 。所以 。
因此,当 为奇数的时候,,否则,。
由于奇数和奇数,偶数和偶数不会连续,所以没有 的情况。
那么这一个
Subtask可以在 的时间复杂度内完成。在这一个
Subtask里,,所以可以用Subtask #1的性质进行枚举。时间复杂度 。可以通过。在这一个
Subtask里,。直接暴力枚举显然超时。考虑使用前缀和进行优化。
蒟蒻:前缀和是什么?
假设有一个 数列,那么考虑创建一个 序列,满足 并且 ,那么 序列就是 序列的前缀和序列。
区间求和问题:假设要求 的和。
那么可以使用容斥原理。
为 区间的和, 为 区间的和,那么 的值就是 区间的和。
时间复杂度 。模板题。
考虑进行预处理。
容易发现,,其中 判断的是 是否为奇数。
然后询问区间 只需要询问 的值即可。
,那么无法进行 的前缀和。
然后就需要使用性质。
- 奇数和偶数是交替的。
- 如果 ,那么一定有 。
考虑特殊情况: 直接使用
Subtask #1的性质, 进行特殊判断。然后考虑 , 的特殊情况。
容易发现, 和 此时都不满足 的特殊性质。
那么这个时候直接使用公式 即可算出答案。
那如果 , 怎么办?
那么将 进行右移一直到满足 ,并且答案 。
同理,进行左移一直到满足 ,并且答案 。
实际上只需要进行
if判断即可。这个地方一开始想复杂了,使用了while循环。然后就可以在 的时间复杂度内通过这道题了!
超级大常数代码:
// 跑了 163ms // 常数巨大 // 模拟赛因为常数 100->90 可以AFO了 #include <bits/stdc++.h> #define int long long using namespace std; signed main() { int T; cin >> T; while (T --) { int l, r; cin >> l >> r; if (l == r) cout << l % 2 << '\n'; else { int ans = 0; while (l % 2) { ans ++; l ++; } while (r % 2) { ans ++; r --; } if (l > r) cout << ans << '\n'; else { ans += (r - l) / 2; cout << ans << '\n'; } } } return 0; }注意点:不要忘记开
long long!十年 OI 一场空,不开long long见祖宗。
- 1
信息
- ID
- 7623
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 1
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者