Skip to content

好的,主人!交给我吧,喵~ 看我为你细致地讲解这道关于位运算的有趣题目!

A. Mocha and Math | 摩卡和数学


呀哈喽,主人~ 今天我们来看一道摩卡酱遇到的数学难题。不过别担心,有本猫娘在,再复杂的题目也会变得清晰起来的,喵!

题目大意

是这样的喵:我们有一个长度为 n 的数列 a。我们可以对这个数列进行一种神奇的操作,任意次!

操作是这样的:

  1. 选一个区间 [l, r]
  2. 对于区间里的每一个位置 i(从 0r-l),我们同时把 a[l+i] 变成 a[l+i] & a[r-i]。这里的 &按位与 (bitwise AND) 运算哦。

举个例子,如果数组是 [a1, a2, a3, a4, a5],我们选择区间 [2, 5] (也就是从第2个到第5个元素)。 这个区间就像从中间对折一样,a2a5 碰在一起,a3a4 碰在一起。 然后它们就会发生奇妙的反应!

  • a2 变成 a2 & a5
  • a3 变成 a3 & a4
  • a4 变成 a4 & a3
  • a5 变成 a5 & a2

我们的目标是,通过任意次这样的操作,让数组中 最大的那个数 变得 尽可能小。最后,我们只要告诉摩卡酱这个最小的可能的最大值是多少就可以啦!


解题思路

这道题看起来操作很花哨,但其实藏着一个非常简单的核心思想,喵~ 关键就在于这个 & (按位与) 运算的性质。

主人你想想看,对于任意两个非负整数 xyx & y 的结果会是怎样的呢? 它永远 不会超过 xy 中较小的那个数,也就是 x & y <= min(x, y)。 这是因为 & 运算只有在两个数字的二进制位都为 1 时,结果的那一位才为 1。否则就是 0。所以 & 运算的结果只会去掉一些 1,让数字变小或者不变,但绝不会让它变大!

这就告诉我们一个重要的信息:无论我们怎么操作,数组里的任何一个数字都不会变大

那么,一个数最小能变成多少呢? 每次操作,一个数都是和另一个数(或者它自己)进行 & 运算。这意味着,经过若干次操作后,数组中的任何一个元素 a[i] 的最终值,都可以看作是原始数组中 某些元素的按位与 的结果。

既然我们想让数组中最大的数变得最小,那我们就要让数组中所有的数都变得尽可能小。一个数 a[i] 能够达到的理论最小值,就是 原始数组中所有元素的按位与 的结果。让我们把这个结果记为 T 吧! T = a[1] & a[2] & ... & a[n]

为什么呢?因为 T 是所有数字 & 起来的结果,它包含了所有数字二进制表示中的“共同的0”,所以任何一个数都不可能比 T 更小了。因此,我们最终数组中的最大值,一定 大于等于 T

那么问题来了,我们真的能让数组里所有的数都变成 T 吗?如果可以,那答案不就是 T 了嘛!

答案是:可以的,喵!我们可以构造出一种操作方法:

  1. 第一步:让 a[1] 变成 T 我们可以通过一系列操作,把所有元素的信息都汇集到 a[1] 上。

    • 对区间 [1, 2] 操作,a[1] 变成 a[1] & a[2]
    • 接着对区间 [1, 3] 操作,a[1] 变成 (a[1] & a[2]) & a[3]
    • ...
    • 最后对区间 [1, n] 操作,a[1] 就变成了 a[1] & a[2] & ... & a[n],也就是 T 啦!
  2. 第二步:用 a[1] 更新所有其他元素 现在我们有了一个值为 Ta[1],它就像一个“种子”。我们可以用它来把其他所有元素都变成 T

    • 比如想让 a[k] 变成 T,我们只需要对区间 [1, k] 操作。
    • 这次操作中,a[k] 会和 a[1] 发生反应,新的 a[k] 会变成 旧a[k] & a[1]
    • 因为此时 a[1] 的值已经是 T 了,所以 新a[k] = 旧a[k] & T
    • T 本身就是 旧a[k] 和其他所有数 & 起来的结果,所以 旧a[k] & T 的结果就是 T 本身!
    • 通过这种方式,我们可以把 a[2], a[3], ..., a[n] 全部变成 T

既然我们能把数组里所有的元素都变成 T,那么数组中的最大值自然也就是 T 了。 结合我们之前得出的结论(最大值一定 >= T),我们可以肯定地说,我们能达到的最小的最大值,就是 T

所以,这道题的答案就是把输入的所有数字全部按位与起来,是不是超级简单呀,喵呜~


题解代码

这是实现这个想法的 C++ 代码,非常简洁哦!

cpp
#include <iostream>

void solve() {
    int n;
    std::cin >> n;

    // 根据我们的分析,最小的可能的最大值就是所有元素的按位与结果。
    // 我们只需要计算 a_1 & a_2 & ... & a_n 就可以啦。

    int result;
    // 先读入第一个数,作为我们计算的初始值,喵~
    std::cin >> result;

    // 然后循环读入剩下的 n-1 个数
    for (int i = 1; i < n; ++i) {
        int current_val;
        std::cin >> current_val;
        // 每读入一个数,就和当前的结果进行按位与运算
        result &= current_val;
    }

    // 最后,result 里存的就是所有数字按位与的结果啦,直接输出!
    std::cout << result << "\n";
}

int main() {
    // 这两行是为了让输入输出快一点,就像猫猫跑得一样快,喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int t;
    std::cin >> t;
    while (t--) {
        solve();
    }

    return 0;
}

知识点小课堂

这道题主要用到了一个核心知识点,本猫娘来给你科普一下!

位运算 (Bitwise Operations)

位运算是直接对整数在内存中的二进制位进行操作。这道题用到的就是其中的 按位与 (Bitwise AND),在 C++ 和很多语言中用 & 符号表示。

工作原理& 运算会对两个数字的二进制表示的每一位进行比较。只有当两个数字在同一位上都为 1 时,结果的该位才为 1,否则为 0

举个栗子:计算 11 & 7

  1. 把数字转换成二进制:
    • 11 的二进制是 1011
    • 7 的二进制是 0111
  2. 对齐它们的二进制位,然后逐位进行 & 运算:
      1 0 1 1  (11)
    & 0 1 1 1  (7)
    -----------
      0 0 1 1  (3)
  3. 结果 0011 转换回十进制就是 3。所以 11 & 7 = 3

重要性质: 就像我们在解题思路里说的那样,& 运算最重要的性质之一就是 x & y <= x 并且 x & y <= y。这个性质是解决这道题的突破口,它保证了数值只减不增,让我们能够大胆地去寻找那个理论上的最小值,喵~

希望这次的讲解能帮到主人!如果还有其他问题,随时都可以来找我哦!喵~

Released under the MIT License.