『题解』CodeForces CF92B Binary Number

题目传送门

题目大意

给定一个二进制数 \(x\),有两种操作:

  1. 如果 \(x\) 为奇数,将 \(x\)\(1\)
  2. 如果 \(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;
}
posted @ 2022-01-21 21:16  仙山有茗  阅读(45)  评论(0编辑  收藏  举报