为了证明 leetcodehard 题完全不 hard,想要抨击唯 leetcode 是论的思想,昨天给可靠大先辈讲子集枚举DP的时候随便找了道 leetcode 的题,今天随手写一发。

p.s. leetcode 不用 stdin / stdout 是什么傻逼 OJ (暴论)

Leetcode 600 不含连续1的非负整数

题面

给定一个正整数n,找出小于或等于n的非负整数中,其二进制表示不包含连续的1的个数。

示例 1:

输入: 5
输出: 5
解释: 
下面是带有相应二进制表示的非负整数<= 5:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
其中,只有整数3违反规则(有两个连续的1),其他5个满足规则。

说明: 1 <= n <= 1e9

题解

好的,看了这道题,由于我日常做法剑走偏锋,从来不按规矩正常做,所以就定义了一个恶臭 DP:

$$
f[i, 0] = f[i - 1, 0] + f[i - 1, 1]
$$

$$
f[i, 1] = f[i - 1, 0]
$$

$$
f[0, 0] = f[0, 1] = 1
$$

让我来解释一下上面这个方程: $f[i, 0]$ 表示一个长度为 $i$ 的二进制串,最高位为 $0$,按照题目说的不含连续1的数的方案数。$f[i, 1]$ 就是上面这句话除了最高位是 $1$。

注意:为了写代码方便,我代码中好像 $f[i, 0]$ 代表的是长度为 $i + 1$,因为这样第零位就是权为 $2^0$ 的那位。嘛...无所谓,怎么方便怎么来。下面的解释中都会按照长度是 $i + 1$来。

我觉得转移方程非常生动形象,无需解释,那么我们就来看一下答案统计。

由于答案是要小于等于某个数,无疑不能直接得到。但是,这并没有个关系,我们可以稍稍 hack 一下。我们用下面的两个二进制串举例,分别代表两种情况:

$$
100101000101
$$

$$
100111011011
$$

我直接说吧,他们的区别就是其中有没有连续的 1,先看上面这种没有连续的 1

$$
100101000101
$$

我们可以拆分为如下:

$$
\begin{aligned}
&000000000000 \sim 100000000000 - 1 = 011111111111 \\
&100000000000 \sim 100100000000 - 1 = 100011111111 \\
&100100000000 \sim 100101000000 - 1 = 100100111111 \\
&100101000000 \sim 100101000100 - 1 = 100101000011 \\
&100101000100 \sim 100101000101 \\
\end{aligned}
$$

可以发现,从上至下,方案书分别为:

$$
f[10, 0], f[8, 0], f[6, 0], f[2, 0], f[0, 0] + 1
$$

除了最后一个有$+1$,其他都是最低的1的位置$n$的$f[n, 0]$。

所以我们就能很轻松地进行答案统计:

$$
\text {ans} = 1 + \sum _ {0 \leq k < |D|} f[d[k], 0]
$$

其中,$D$表示所有数位是1的位数的集合。

再看有连续的 1 的情况:

$$
100111011011
$$

当我们想要如法炮制的时候会发现:

$$
\begin{aligned}
&000000000000 \sim 100000000000 - 1 = 011111111111 \\
&100000000000 \sim 100100000000 - 1 = 100011111111 \\
&100100000000 \sim 100110000000 - 1 = 100101111111 \\
&\color{red}{100110000000 \sim 100111000000 - 1 = 100110111111}
\end{aligned}
$$

最后一行就出现了问题。这个区间出现了重复的 1。然而!这并没有什么大不了的,这反而更加简单了,因为它直接退化了。因为我们根本不可能达到最大数 $100111011011$,相反,我们最大可以达到的只有:

$$
1001011111111
$$

因为我们是从高位往低位考察的,所以这个时候,我们只需要加上那个 $f[i, 0]$ 然后直接退出循环就行,省时省力,判断条件即为这一位和上一位是否都是 1.

下面是正常的代码:

#include <iostream>

using namespace std;
typedef long long ll;
const int maxn = 35;

int d[maxn], dc;
ll a, f[maxn][2];

int main() {
    cin >> a;
    while (a > 0) {
        d[dc ++] = a & 1;
        a >>= 1;
    }

    f[0][1] = 1;
    f[0][0] = 1;
    for (int i = 1; i < dc + 2; i ++) {
        f[i][1] = f[i - 1][0];
        f[i][0] = f[i - 1][1] + f[i - 1][0];
    }

    ll ans = 0;

    int last = dc - 1;
    bool flag = false;
    ans += f[last][0];
    for (int i = dc - 2; i >= 0; i --) {
        if (d[i]) {
            ans += f[i][0];
            if (i == last - 1) {
                flag = true;
                break;
            }
            else last = i;
        }
    }
    if (!flag) ans ++;
    cout << ans << endl;
    return 0;
}

下面是恶臭 leetcode 的代码:

const int maxn = 35;

class Solution {
public:
    int d[maxn], dc = 0;
    int f[maxn][2];

    int findIntegers(int n) {
        while (n > 0) {
            d[dc ++] = n & 1;
            n >>= 1;
        }

        f[0][1] = 1;
        f[0][0] = 1;
        for (int i = 1; i < dc + 2; i ++) {
            f[i][1] = f[i - 1][0];
            f[i][0] = f[i - 1][1] + f[i - 1][0];
        }

        int ans = 0;
        int last = dc - 1;
        bool flag = false;
        ans += f[last][0];
        for (int i = dc - 2; i >= 0; i --) {
            if (d[i]) {
                ans += f[i][0];
                if (i == last - 1) {
                    flag = true;
                    break;
                }
                else last = i;
            }
        }
        if (!flag) ans ++;
        return ans;
    }
};

测试传送门:https://leetcode-cn.com/problems/non-negative-integers-without-consecutive-ones/

最后修改:2021 年 09 月 11 日 02 : 42 PM
真的不买杯奶茶嘛....qwq