徐老师的二进制加法

【题目来源】

瑞学堂:徐老师的二进制加法

【题目描述】

徐老师最近刚刚学习了二进制加法,现在他希望自己出一些题目来锻炼一下自己。

他先随便写了一个 \(n\) 位的二进制数字 x。

接下来他会进行 \(m\) 次加法运算,每次运算就是给 \(x\) 加上 \(2^k\) 对应的二进制数字。

但是他突发奇想,想知道每次运算后有多少位会变化,并且最终 \(x\) 的值是多少。你能帮他完成这些计算吗?

【输入】

输入第一行包含一个整数 \(n\) 表示二进制位数

输入第二行一个长度为 \(n\) 的二进制数字 \(x\),每位只有 \(0/1\)

接下来一个整数 \(m\) 表示徐老师要进行加法的次数

接下来 \(m\) 行,每行一个整数 \(k\) 表示这次加法要加的数字为 \(2^k\) 对应的二进制数字

【输出】

对于每次加法运算,输出一行一个整数,表示此次运算后发生变化的位数。
所有运算结束后,输出一行一个二进制字符串,表示最终的 \(x\) 的值(不含前导零)。

【输入样例】

3
110
6
2
2
1
2
2
2

【输出样例】

2
1
4
1
2
1
11100

【核心思想】

  1. 问题分析:给定一个 \(n\) 位二进制数 \(x\)(低位在前存储),进行 \(m\) 次加法,每次加 \(2^k\)。要求输出每次运算后发生变化的位数,以及最终 \(x\) 的值(不含前导零)。这是一个高精度二进制加法模拟问题,关键在于利用二进制加 \(2^k\) 的特殊性:只会影响从第 \(k\) 位开始连续的 \(1\) 段。

  2. 算法选择

    • 高精度二进制存储:用数组 \(a\) 倒序存储(\(a[1]\) 为最低位),支持动态扩位
    • 进位链模拟:加 \(2^k\) 时,从第 \(k+1\) 位开始,连续的 \(1\) 变为 \(0\)(进位),直到遇到第一个 \(0\) 变为 \(1\)
    • 变化位数统计:进位链中每个 \(1 \to 0\) 和最终的 \(0 \to 1\) 都计入变化
  3. 关键步骤

    • 读入与初始化:读入 \(n\) 和二进制字符串 \(s\),将 \(s\) 倒序存入 \(a[1..n]\)\(a[0]\) 存位数)
    • 处理每次加法(读入 \(k\),转换为下标 \(x = k+1\)):
      • 进位链遍历:当 \(a[x] = 1\) 时,a[x] = 0change++x++(向高位进位)
      • 终止进位a[x] = 1change++(该位由 \(0\)\(1\)
      • 更新位数:若 \(x > a[0]\),则 a[0] = x
      • 输出 change
    • 去除前导零:当 \(a[0] > 1\) 且最高位 \(a[a[0]] = 0\) 时,--a[0]
    • 输出结果:从 \(a[a[0]]\)\(a[1]\) 倒序输出
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + m + \text{总进位次数})\),每次加法最坏 \(O(n)\),但均摊接近 \(O(1)\)(每位从 \(1\)\(0\) 后需再从 \(0\)\(1\) 才能再次进位)
    • 空间复杂度:\(O(n + m)\),高精度数组存储
  5. 二进制加法模拟的核心思想

    • \(2^k\) 的局部性:与普通高精度加法不同,加 \(2^k\) 只影响从第 \(k\) 位开始的高位,低位完全不变,利用此特性避免全数组遍历
    • 进位链的连续段处理:二进制中 \(1+1=0\) 并产生进位,因此连续的 \(1\) 会形成"全变 \(0\)"的链式反应,直到遇到第一个 \(0\) 吸收进位
    • 变化位数的精确统计:进位链中每个 \(1 \to 0\) 贡献 \(1\) 次变化,最终的 \(0 \to 1\) 贡献 \(1\) 次变化,总和为"连续 \(1\) 的个数 \(+ 1\)"
    • 前导零的动态维护:通过 while 循环在输出前去除最高位的 \(0\),保证输出格式正确
    • 适用于高精度二进制数的单点增量操作,核心在于利用二进制进位链的连续性实现高效模拟

【算法标签】

模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 2000005;   // 定义数组最大容量为2000005(不能按照题意的1000005,需要开大点)
int n, m;                // n为二进制位数,m为加法运算次数
int a[N], b[N];          // a数组存储高精度二进制数(a[0]为位数,a[i]为第i位的值);b数组未使用
char s[N];               // s临时存储输入的二进制字符串

int main()
{
    scanf("%d", &n);     // 读入二进制位数n
    scanf("%s", s);      // 读入n位二进制数字x(字符串形式)
    memset(a, 0, sizeof(a));  // 将a数组清零
    a[0] = n;            // a[0]存储当前二进制数的位数
    // 将字符串s倒序存入a数组(低位在前,高位在后)
    // s[0]是最高位,对应a[n];s[n-1]是最低位,对应a[1]
    for (int i = 1; i <= n; i++)
        a[i] = s[n - i] - '0';  // 字符'0'/'1'转换为数字0/1
    scanf("%d", &m);     // 读入加法运算次数m
    while (m--)          // 依次处理每次加法运算
    {
        int x;
        scanf("%d", &x);  // 读入k,表示要加2^k
        x++;              // 将k转换为数组下标(a[1]对应2^0,所以k对应下标k+1)
        int change = 0;   // change记录此次运算发生变化的位数
        // 模拟二进制加法:从第x位开始,连续的1变为0(进位),直到遇到第一个0
        while (a[x] == 1)  // 如果当前位为1,加1后变为0,继续向高位进位
        {
            a[x] = 0;      // 该位由1变0
            change++;      // 变化位数加1
            x++;           // 向高位进位
        }
        // 遇到第一个0,将其变为1(进位结束)
        a[x] = 1;
        change++;          // 该位由0变1,变化位数加1
        // 如果进位超出了当前最高位,更新位数
        if (x > a[0])
            a[0] = x;      // 更新二进制数的总位数
        printf("%d\n", change);  // 输出此次运算发生变化的位数
    }
    // 去除前导零:当位数大于1且最高位为0时,减少位数(重要!怀疑输入的数据就有前导零)
    while (a[0] > 1 && !a[a[0]])
        --a[0];
    // 输出最终的二进制数(从高位到低位)
    for (int i = a[0]; i >= 1; i--)  // 从最高位到最低位遍历
    {
        printf("%d", a[i]);  // 输出每一位
    }
    printf("\n");            // 输出结束后换行
    return 0;
}

【运行结果】

3
110
6
2
2
2
1
1
4
2
1
2
2
2
1
11100
posted @ 2026-08-06 17:01  团爸讲算法  阅读(3)  评论(0)    收藏  举报