徐老师的二进制加法
【题目来源】
瑞学堂:徐老师的二进制加法
【题目描述】
徐老师最近刚刚学习了二进制加法,现在他希望自己出一些题目来锻炼一下自己。
他先随便写了一个 \(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
【核心思想】
-
问题分析:给定一个 \(n\) 位二进制数 \(x\)(低位在前存储),进行 \(m\) 次加法,每次加 \(2^k\)。要求输出每次运算后发生变化的位数,以及最终 \(x\) 的值(不含前导零)。这是一个高精度二进制加法模拟问题,关键在于利用二进制加 \(2^k\) 的特殊性:只会影响从第 \(k\) 位开始连续的 \(1\) 段。
-
算法选择:
- 高精度二进制存储:用数组 \(a\) 倒序存储(\(a[1]\) 为最低位),支持动态扩位
- 进位链模拟:加 \(2^k\) 时,从第 \(k+1\) 位开始,连续的 \(1\) 变为 \(0\)(进位),直到遇到第一个 \(0\) 变为 \(1\)
- 变化位数统计:进位链中每个 \(1 \to 0\) 和最终的 \(0 \to 1\) 都计入变化
-
关键步骤:
- 读入与初始化:读入 \(n\) 和二进制字符串 \(s\),将 \(s\) 倒序存入 \(a[1..n]\)(\(a[0]\) 存位数)
- 处理每次加法(读入 \(k\),转换为下标 \(x = k+1\)):
- 进位链遍历:当 \(a[x] = 1\) 时,
a[x] = 0,change++,x++(向高位进位) - 终止进位:
a[x] = 1,change++(该位由 \(0\) 变 \(1\)) - 更新位数:若 \(x > a[0]\),则
a[0] = x - 输出
change
- 进位链遍历:当 \(a[x] = 1\) 时,
- 去除前导零:当 \(a[0] > 1\) 且最高位 \(a[a[0]] = 0\) 时,
--a[0] - 输出结果:从 \(a[a[0]]\) 到 \(a[1]\) 倒序输出
-
时间/空间复杂度:
- 时间复杂度:\(O(n + m + \text{总进位次数})\),每次加法最坏 \(O(n)\),但均摊接近 \(O(1)\)(每位从 \(1\) 变 \(0\) 后需再从 \(0\) 变 \(1\) 才能再次进位)
- 空间复杂度:\(O(n + m)\),高精度数组存储
-
二进制加法模拟的核心思想:
- 加 \(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
浙公网安备 33010602011771号