题解 | #小q的数列#

小q的数列

https://www.nowcoder.com/practice/76796815518f4db5b800775581cda1e4

所有C语言题解中,排名第一的代码

理解题目:

这个题目围绕一个递推数列 的性质展开,分为两个任务:

  1. 计算数列的第 的值。

    • 数列的递推公式为:

      • f[0] = 0
      • f[1] = 1
      • f[i] = f[i / 2] + f[i % 2],其中 i >= 2。
    • 这个公式的本质:

      • f[i] 的值是由 i 在二进制表示中 1 的个数决定的。i/2 等价于右移一位,i%2 等价于最低位是否是 1。
      • 所以,f[i] 等于 i 的二进制表示中 1 的个数。
    • 例子:

      f[0] = 0   (二进制 0,1 的个数为 0)
      f[1] = 1   (二进制 1,1 的个数为 1)
      f[2] = 1   (二进制 10,1 的个数为 1)
      f[3] = 2   (二进制 11,1 的个数为 2)
      f[4] = 1   (二进制 100,1 的个数为 1)
      
  2. 找出 首次出现在数列中的最小位置

    • 如果 f[n] = k,需要输出 f[i] = k 时的最小 i。

    • 这个值 实际上对应于二进制中恰好包含 k 个 1 的最小数。比如:

      f[3] = 2,首次出现值为 2 的位置是 3(11 二进制全是 1)。
      f[6] = 2,值为 2 的最小位置仍然是 3。
      f[15] = 4,值为 4 的最小位置是 15(二进制全是 4 个 1)。
      

常规解题代码

#include <stdio.h>

int main() {
    long long t; // 操作次数
    scanf("%lld", &t);

    while (t--) {
        long long n;
        scanf("%lld", &n);

        // 计算二进制中1的个数
        long long sum = 0, temp = n;
        while (temp) {
            sum += temp & 1;
            temp >>= 1;
        }

        // 计算二进制全为1的最小数
        long long num = (1LL << sum) - 1;

        // 输出结果
        printf("%lld %lld\n", sum, num);
    }

    return 0;
}

优化解决方案:

1. 计算 f[n] 的值

  • 利用二进制中 1 的个数快速计算 f[n]:
    sum = __builtin_popcountll(n);
    
    这是一种高效的位运算,用于统计二进制中 1 的个数。

2. 计算 n' 的值

  • n' 的定义是满足 f[n'] = f[n] 的最小值,其中 n' 二进制全为 1,且 1 的个数等于 f[n]。
  • 构造这个数的方法:
    • 如果 f[n] = k,则 n' = 2^k - 1,因为 2^k - 1 的二进制是 k 个 1 组成。

优化思路:

  • 对于大范围的 n 和 t,直接逐项计算 f[i] 不可行,因此需要 O(1) 的方法:
    1. f[n] 用位运算直接得到。
    2. n' 用公式 (1LL << f[n]) - 1 快速构造。

最终代码实现:

#include <stdio.h>

int main() {
    int t; // 测试用例数量
    scanf("%d", &t);

    while (t--) {
        long long n;
        scanf("%lld", &n);

        // 计算 f[n],即 n 的二进制中 1 的个数
        int sum = __builtin_popcountll(n);

        // 计算 n',即 sum 个 1 的二进制数
        long long n_prime = (1LL << sum) - 1;

        // 输出结果
        printf("%d %lld\n", sum, n_prime);
    }

    return 0;
}

代码解读:

  1. __builtin_popcountll(n)

    • 这是 GCC 提供的内建函数,用于快速统计 long long 类型变量中二进制位 1 的个数。
    • 复杂度接近 O(1)。
  2. (1LL << sum) - 1

    • 生成二进制中全为 1 的数字,1LL << sum 等价于 2^sum。
  3. 循环处理每个测试用例

    • 对每个输入的 n,计算 f[n] 和 n' 并输出。

复杂度分析:

  • 时间复杂度:O(t),每个 n 的计算只需 O(1)。
  • 空间复杂度:O(1)。

详细介绍生成二进制全为 1 的表达式

表达式 (1LL << sum) - 1 是一种典型的位操作,主要用于生成一个特定长度的位掩码或用于其他类似的场景。详细解释:

表达式分解

  1. 1LL:

    • 这里的 1LL 表示一个长整型(long long)的整数值 1。
    • 加 LL 的目的是为了确保它是一个 64 位的整数,防止位操作时溢出或引起未定义行为(尤其是在 C/C++ 中)。
  2. << sum:

    • << 是位左移运算符。
    • 将 1LL 的二进制位向左移动 sum 位。
    • 例如:
      • 若 sum = 3,则 1LL << 3 结果是 1000(二进制),即 8(十进制)。
      • 若 sum = 5,则 1LL << 5 结果是 100000(二进制),即 32(十进制)。
  3. - 1:

    • 最后将结果减去 1。
    • 减去 1 的效果是将最高位的 1 减去,并将剩下的低位全部置为 1。
    • 例如:
      • 若 1LL << 3 = 1000,则 (1LL << 3) - 1 = 0111(二进制),即 7(十进制)。
      • 若 1LL << 5 = 100000,则 (1LL << 5) - 1 = 011111(二进制),即 31(十进制)。

作用

这个表达式的作用是生成一个二进制的掩码,其低位有 sum 个连续的 1。例如:

  • 如果 sum = 3,结果是 0111(二进制,十进制为 7)。
  • 如果 sum = 5,结果是 011111(二进制,十进制为 31)。
  • 如果 sum = 10,结果是 0000001111111111(二进制,十进制为 1023)。
全部评论

相关推荐

不愿透露姓名的神秘牛友
今天 10:52
点赞 评论 收藏
分享
object3:开始给部分🌸孝子上人生第一课了
点赞 评论 收藏
分享
粗心的雪碧不放弃:纯学历问题,我这几个月也是一直优化自己的简历,后来发现优化到我自己都觉得牛逼的时候,发现面试数量也没有提升,真就纯学历问题
点赞 评论 收藏
分享
1 收藏 评论
分享
牛客网
牛客企业服务