P8567

· · 题解

这一篇题解的目标:让初学 OI 的蒟蒻也能理解这道题,并且拿到 100 分的好成绩。

\texttt{Part 0:} f 函数的含义

int f(int x){
    int ans = 0;
    while (x % 2 == 0){
        x /= 2;
        ans += 1;
    }
    return ans;
}

进一步分析这个函数:

这是一个循环,当 x\bmod 2 = 0,也就是当 x 是奇数的时候,停止循环并且返回计数器,否则进入循环,计数器自增 1,并且让 x 除以 2

蒟蒻:咦?这是什么?

那么这样解释:

将一个数 x 转换成二进制,求 x 的末尾有多少个连续的 0

如果一个数 x=11,那么 x 的二进制是 (1011)_2,容易发现末尾没有 0,也就是说,f(11)=0

如果一个数 x=12,那么 x 的二进制是 (1100)_2,容易发现末尾有两个 0。也就是说,f(12)=2

由于 x /= 2 这一句话可以看做 x >>= 1,也就是将二进制右移一位。所以这个程序就是求解二进制的末尾有多少个连续的 0 的。

进一步理解,x >>= 1 可以看做 x /= 2,那么求二进制的末尾有多少个连续的 0,就可以看做是求 x 里面有多少个因子 2

\texttt{Part 1: Subtask \#1}

在这一个 Subtask 里,l=r。也就是说,我们有一些不同的方法:

方法 1:直接判断 f(l)f(l+1) 的大小。但是有一点麻烦,并且时间复杂度较高,是 O(T\log x) 的。

方法 2:考虑 f 函数的性质。

容易发现,如果 x 是一个奇数,那么 f(x)=0

原因:奇数没有因子 2

同理,偶数有因子 2。所以 f(x+1) > 0

因此,当 l 为奇数的时候,f(l) < f(l+1),否则,f(l) > f(l+1)

由于奇数和奇数,偶数和偶数不会连续,所以没有 f(l) = f(l+1) 的情况。

那么这一个 Subtask 可以在 O(T) 的时间复杂度内完成。

\texttt{Part 2: Subtask \#2}

在这一个 Subtask 里,r-l \le 10^3,所以可以用 Subtask #1 的性质进行枚举。时间复杂度 O(T\times (r-l))。可以通过。

\texttt{Part 3: Subtask \#3}

在这一个 Subtask 里,l\le r\le 10^6。直接暴力枚举显然超时。

考虑使用前缀和进行优化。

蒟蒻:前缀和是什么?

假设有一个 a 数列,那么考虑创建一个 b 序列,满足 b_i = b_i-1 + a_i 并且 b_i = 0,那么 b 序列就是 a 序列的前缀和序列。

区间求和问题:假设要求 [l,r] 的和。

那么可以使用容斥原理。

时间复杂度 $O(1)$。[模板题](https://www.luogu.com.cn/problem/B3612)。 考虑进行预处理。 容易发现,$b_r = b_{r-1} + g(r)$,其中 $g(r)$ 判断的是 $r$ 是否为奇数。 然后询问区间 $[l,r]$ 只需要询问 $b_r - b_{l-1}$ 的值即可。 ## $\texttt{Part 4: Subtask \#4} 然后就需要使用性质。 + 奇数和偶数是交替的。 + 如果 $f(x) < f(x+1)$,那么一定有 $f(x + 1) > f(x + 2) < f(x + 3)$。 考虑特殊情况:$l=r$ 直接使用 `Subtask #1` 的性质,$l+1 = r$ 进行特殊判断。 然后考虑 $l\bmod 2 = 0$,$r\bmod 2 = 0$ 的特殊情况。 容易发现,$l$ 和 $r$ 此时都不满足 $f(x) < f(x+1)$ 的特殊性质。 那么这个时候直接使用公式 $\lfloor\frac{r-l}{2}\rfloor$ 即可算出答案。 那如果 $l\bmod 2 = 1$,$r\bmod 2 = 1$ 怎么办? 那么将 $l$ 进行右移一直到满足 $l\bmod 2 = 0$,并且答案 $+1$。 $r$ 同理,进行左移一直到满足 $r\bmod 2 = 0$,并且答案 $+1$。 实际上只需要进行 `if` 判断即可。这个地方一开始想复杂了,使用了 `while` 循环。 然后就可以在 $O(T)$ 的时间复杂度内通过这道题了! 超级大常数代码: ```cpp // 跑了 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` 见祖宗。