[语言月赛 202401] 二进制与一 的题解
记住只在没有思路时使用题解,不要从它复制粘贴代码。请尊重题目和题解的作者。
在解题之前提交题解的代码会导致封禁。
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求依次处理 次操作,每次给出一个正整数
,求出使当前
的二进制表示中从右往左数第
位变为
的最小非负增量
,每次操作后
更新为
,最后输出全部
之和。关键在于第
位的权值为
,只需取出
的低
位(即
)与它比较,就能在常数时间内直接算出每次的
,总复杂度为
。
分析
核心观察
二进制中从右往左数第 位的权值是
,所以第
位是否为
完全由
的低
位决定,与更高的位无关。记
,则第
位为
等价于
,第
位为
等价于
。
思路
由于更高的位不影响第 位,每次操作只需关注低
位
。
当 时第
位已经是
,无需任何改动,取
就是最小值。
当 时,要让第
位变成
,低
位至少要涨到
;而低
位每增大
,整个数也恰好增大
,因此增量不可能小于
。取
时,低 位恰好变成
,即二进制下的
(共
位,第
位为
、其余为
)。又因为
,加上去不会向第
位进位,高位保持不变,所以这样的
既可行又最小。
两种情形可以统一写成
算出后令 ,再处理下一次操作。
朴素做法是每次从当前 出发不断加
,直到第
位变成
,单次最坏需要约
次加法,
次的总运算量无法承受;利用低
位的性质可以把单次操作降到
,总复杂度
。
具体示例
样例中 ,
,三次操作的
依次为
。
- 第
次操作,
:
,
,故
,
变为
(二进制
)。
- 第
次操作,
:
,
,第
位已是
,故
,
保持
。
- 第
次操作,
:
,
,故
,
变为
。
三次增量之和为 ,与样例输出一致。
算法步骤
- 读入
n与q,初始化sum为。
- 重复
q次,每次读入k。 - 令
pw等于,即第
位的权值;令
r等于,即
的低
位。
- 若
r小于pw,则令x等于pw与r之差,把x累加进sum,并令n增加x;否则不做任何修改。 - 输出
sum。
复杂度分析
- 时间复杂度:
,每次操作只含常数次算术运算。
- 空间复杂度:
,只需常数个变量。
实现注意事项
可以取到
,而
时要用
取模,两者都超出了
位有符号整型的表示范围,读入
必须使用
位整型(long long)。
- 单次增量最大为
(
且
时取到),在
次操作后总和的上界约为
,同样超出
位整型的范围,因此累加变量
也必须使用
位整型;若用
位整型累加,在
的数据上会溢出回绕,得到错误答案。
- 计算
时要用
位的字面量参与移位,若用
位字面量左移,在
较大时结果就不是正确的权值了;
可由
直接左移一位得到,不必重复计算。
- 判断必须取严格小于:低
位小于
时才需要加增量,大于或等于时第
位已经是
,增量取
,容易写成非严格比较而多算。
- 输入最多
行,C++ 用 scanf 依次读入即可满足时限;Python 的整数为任意精度,不存在溢出问题,但建议一次性读入全部输入后再切分,以减少读入开销。
源代码
#include <cstdio>
int main() {
long long n = 0, q = 0;
scanf("%lld %lld", &n, &q);
long long sum = 0;
for (long long i = 0; i < q; ++i) {
int k = 0;
scanf("%d", &k);
const long long pw = 1LL << (k - 1); // 2^(k-1),第 k 位的权值
const long long r = n % (pw << 1); // n mod 2^k,n 的低 k 位
if (r < pw) {
const long long x = pw - r;
sum += x;
n += x;
}
}
printf("%lld\n", sum);
return 0;
}
import sys
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0])
q = int(data[1])
total = 0
for i in range(q):
k = int(data[2 + i])
pw = 1 << (k - 1) # 2^(k-1),第 k 位的权值
r = n % (pw << 1) # n mod 2^k,n 的低 k 位
if r < pw:
x = pw - r
total += x
n += x
print(total)
main()
评论