『题解』CodeForces CF92B Binary Number
题目大意
给定一个二进制数 \(x\),有两种操作:
- 如果 \(x\) 为奇数,将 \(x\) 加 \(1\)。
- 如果 \(x\) 为偶数,将 \(x\) 除以 \(2\)。
求将 \(x\) 变为 \(1\) 的操作次数。
思路
先来看存储问题。最简单的想法就是直接将二进制转换成整数,但是请看数据范围:\(x\) 的位数不超过 \(10^6\)。也就是说,这个数最大为 \(2^{10^6}\) 连 __uint128_t
也存不下。
那么就只能使用一个 \(char\) 类型数组 \(s\) 存放二进制数了,操作数最大应该为 \(10^6 \times 2\),也就是操作这么多次,时间复杂度 \(\mathcal{O}(10^6 \times 2)\) 可以过。
直接按照题意模拟就行。
使用一个变量 \(p\) 记录二进制数在 \(s\) 中最后一位的位置,每次操作时要做相应的修改,例如除以二,直接舍去最后一位,使 \(p\) 加一即可。\(ans\) 记录操作次数,也就是最后的答案。
只要 \(p\) 不为 \(0\),也就是 \(s\) 的位数不止 \(1\) 位了,这个数就不为 \(0\),我们就需要对它进行操作。
如何判断奇数和偶数呢?是个问题,我们来找找规律。
- 二进制数
101
,十进制 \(5\),是奇数。 - 二进制数
1000
,十进制 \(8\),是偶数。 - 二进制数
1010
,十进制 \(10\),是偶数。 - 二进制数
1011
,十进制 \(11\),是奇数。 - 二进制数
11101
,十进制 \(29\),是奇数。 - ......
你会发现,所有最后一位为 \(1\) 的二进制数都是奇数,其余都为偶数。
我们只要判断 \(s\) 的最后一位 \(s_p\) 是 \(1\) 还是 \(0\) 就可以知道 \(x\) 是奇数还是偶数了。
那么除以 \(2\) 呢?二进制除以 \(2\) 就等于右移一位。
这里告诉大家一个小技巧:如果想让一个数除以 \(2^n\) 次方,就可以让它右移 \(n\) 位。如果想让一个数乘 \(2^n\) 次方,就可以让它左移 \(n\) 位。
众所周知,位运算比普通运算快。
操作1:如果 \(x\) 为奇数,将 \(x\) 加 \(1\)。
如果 \(x\) 是奇数,那么最后一位一定是 \(1\),再加上 \(1\),就一定会进位。
因此,我们将 \(s_p\) 置为 \(0\),\(s_{p-1}\) 加上 \(1\)。
我们简单一点,\(s_p\) 为 \(0\) 了,那就是偶数情况,可以直接除以 \(2\) 并让操作次数 \(ans\) 加 \(1\),不需要在下一次循环再除以 \(2\) 了,省些时间。
case '1': s[p-1]++; s[p]=0; p--; ans++; break;
你会发现,这样的话 \(s_{p-1}\) 可能为 \(2\),也就是二进制 10
,这不会 \(WA\) 吗?
别急,很简单,我们每次操作时,判断一下当前值为 \(2\) 的情况,如果为 \(2\),就让 \(s_{p-1}\) 加上 \(1\),因为当前这个数为 10
,前一位也有份。
加上之后,这一位要减 \(2\),因为二进制第 \(n\) 位的值为 \(2^n \times x_n\),\(n\) 的初始下标为 \(0\)。这样的话这一位就只剩 \(0\) 了。
呃不对啊,这不是也算了一次操作次数吗?没事,进行这个操作后,\(s_p\) 为 \(0\),可以直接除以 \(2\) 来补上这次操作。
case '2': s[p-1]++; s[p]-=2; p--; break;
嘿嘿,好像很简单的样子,我们可以对代码简化一下。
- 对于奇数情况加 \(1\),我们不用管当前 \(s_p\),不需要真的置 \(0\),直接先让 \(x\) 除以 \(2\),也就是 \(p\) 减 \(1\),再将现在的 \(s_p\) (也就是原来的 \(s_{p-1}\))加 \(1\)。
前三句代码可直接化为一句s[--p]++
。
case '1': s[--p]++; ans++; break;
- 对于当前 \(s_p\) 为 \(2\) 的情况,同理,既然要除以 \(2\),就不用管原来的 \(s_p\) 了,化简代码如下。
case '2': s[--p]++; break;
操作2:如果 \(x\) 为偶数,将 \(x\) 除以 \(2\)。
这个就很简单了,直接将 \(p\) 减 \(1\) 就行了。
case '2': p--; break;
注意
当只剩一位数时,奇数操作可能让这一位数是 \(2\),我们在循环结束时需要判断一下,如果是,ans++
就行了。
代码
#include <iostream>
#include <cstring> // strlen函数
using namespace std;
int main(){
int p,ans=0; // p:pos简称,记录最后一位在哪;ans:记录操作次数
char s[1000005];
cin >> s;
p=strlen(s)-1; // 取长度-1,刚好有值
while(p){ // 只要下标不为0,就说明可以操作,位数为0,就是1或2
ans++; // 每次循环操作一次,ans++
switch(s[p]){ // 不想写if
case '0': p--; break; // 除以二,直接舍弃最后一位
case '1': s[--p]++; ans++; break; // 相当于除以二了,所以再加一次答案
case '2': s[--p]++; break; // 进位操作,2代表当前位为“10”,当前值为0,所以将上一位加一后舍弃就行了
default: break; // 写不写都没事,但是我习惯写一下
}
}
if(s[p]=='2') ans++; // 如果是2,这个二进制就有两位,要除以2,所以ans++
cout << ans << endl;
return 0;
}