• 【每日一题】AcWing 5271. 易变数 | 思维 | 中等


    题目内容

    原题链接

    给定一个二进制表示的数 s s s
    定义函数 f ( x ) f(x) f(x) x x x 的二进制位中为 1 1 1 的数量。每次操作可以使得 x → f ( x ) x\rightarrow f(x) xf(x) ,问在最少操作次数下,恰好 k k k 次操作后为 1 1 1 的数有多少,答案对 1 0 9 + 7 10^9+7 109+7 取模

    数据范围

    • 1 ≤ s < 2 1000 1\leq s<2^{1000} 1s<21000
    • 0 ≤ k ≤ 1000 0\leq k\leq 1000 0k1000

    题解

    这个数 s s s 的上限很大,该怎么考虑呢?

    考虑 x x x 2 1000 − 1 2^{1000}-1 210001 时,其二进制位为 1000 1000 1000 1 1 1 f ( x ) = 1000 f(x)=1000 f(x)=1000

    所以只需要暴力计算下 1000 1000 1000 以内的数到 1 1 1 的最小次数即可。

    定义 d p [ x ] dp[x] dp[x] 表示 x x x 通过 f f f 函数到达 1 1 1 的最小次数。

    状态转移为: d p [ x ] = d p [ f ( x ) ] + 1 dp[x]=dp[f(x)]+1 dp[x]=dp[f(x)]+1

    考虑为 d p [ x ] = k − 1 dp[x]=k-1 dp[x]=k1 x x x ,那么二进制位中 1 1 1 的个数为 x x x 的数,到达 1 1 1 的最小次数恰好为 k k k

    所以我们找到所有 d p [ x ] = k − 1 dp[x]=k-1 dp[x]=k1 x x x ,然后找到小于等于 s s s 的,二进制位中 1 1 1 的数量为 x x x 的所有的数。

    这里考虑到数据范围,很容易联想到数位 d p dp dp

    但是这里由于初始化的问题,容易超时,需要细化数位 d p dp dp 的过程。

    n n n s s s 的二进制表示的长度,

    即如果我们选择 i i i

    • s [ i ] = 1 s[i]=1 s[i]=1

      • 考虑这里选择的数 y y y y [ i ] = 0 y[i]=0 y[i]=0 ,则 y [ i + 1 , ⋯ s − 1 ] y[i+1,\cdots s-1] y[i+1,s1] 都可以为 1 1 1 ,即有 s − i − 1 s-i-1 si1 个位置中,选择若干个位置为 1 1 1 ,这个若干是由剩余可选位置来决定的。
        我们需要有 x x x 1 1 1 ,而 s [ 0 , i ] s[0,i] s[0,i] 中有 m m m 个为 1 1 1 ,那么我们可以选择的就是 m − x m-x mx 位。即 C ( s − i − 1 , m − x ) C(s-i-1,m-x) C(si1,mx)
      • 考虑这里选择的数 y y y y [ i ] = 1 y[i]=1 y[i]=1 ,则后面只能选择 m − x − 1 m-x-1 mx1 1 1 1 了。但是这样直接考虑会超 s s s 的上限,所以将这个问题继续往后抛。直到最后如果 s s s 的二进制位为 1 1 1 的数量恰好为 x x x ,则答案 + 1 。
    • s [ i ] = 0 s[i]=0 s[i]=0 ,则不考虑,因为没有选择, y [ i ] y[i] y[i] 只能为 0 0 0

    上述的整体过程,其实就是数位 d p dp dp 的过程。

    时间复杂度: O ( n 2 ) O(n^2) O(n2)

    代码

    #include 
    using namespace std;
    
    const int MOD = 1e9 + 7;
    int qp(int a, int b) {
        int res = 1;
        while (b > 0) {
            if (b & 1) res = 1ll * res * a % MOD;
            a = 1ll * a * a % MOD;
            b >>= 1;
        }
        return res;
    }
    
    int main()
    {
        string s;
        int k;
        
        cin >> s >> k;
        
        int n = int(s.size());
        
        if (k == 0) {
            cout << "1\n";
            return 0;
        }
        
        if (k == 1) {
            cout << n - 1 << "\n";
            return 0;
        }
        
        vector<int> fac(n + 1), ifac(n + 1);
        fac[0] = 1;
        for (int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % MOD;
        ifac[n] = qp(fac[n], MOD - 2);
        for (int i = n - 1; i >= 0; --i) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % MOD;
        
        auto C = [&](int a, int b) -> int {
            if (b < 0 || a < b) return 0;
            return int(1ll * fac[a] * ifac[b] % MOD * ifac[a - b] % MOD);
        };
        
        int ans = 0;
        
        vector<int> dp(n + 1);
        dp[1] = 0;
        for (int i = 2; i <= n; ++i) {
            int x = i;
            int cnt = 0;
            while (x > 0) {
                cnt += 1;
                x -= x & -x;
            }
            dp[i] = dp[cnt] + 1;
            
            if (dp[i] + 1 == k) {
                
                // 选择 <= s 的,二进制位个数为 i 的数
                // 当前第 j 位为 0 ,还剩 n - j - 1 位,要选择这么些个 1
                int res = 0;
                int one = 0;
                for (int j = 0; j < n; ++j) {
                    if (s[j] == '1') {
                        // 当前第 j 位有两种选择
                        // 第 j 位为 0
                        // 剩余 n - j - 1 位都可以为 1,前面已经占据了 one 个 1,后面再选择 k - one 个即可
                        res = (res + C(n - j - 1, i - one)) % MOD;
                        // 第 j 位为 1,如果 j 不是最后一位,即 j + 1 < n ,那么让 one += 1 ,交由后面的位继续考虑
                        // 如果 j 是最后一位,那么考虑 one + 1 是否等于 k 即可
                        
                        // 对于后面的位来说 one 增加了
                        one += 1;
                    }
                    if (j + 1 == n && one == i) {
                        res = (res + 1) % MOD;
                    }
                }
                ans = (ans + res) % MOD;
            }
        }
        
        cout << ans << "\n";
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
  • 相关阅读:
    C++ [STL之vector模拟实现]
    xargs命令
    HDLBits: 在线学习 SystemVerilog(九)-Problem 36-42
    【Docker 那些事儿】容器网络的 “梦华录”(上篇)
    拍摄视频的时候相机断电导致视频文件损坏,怎么修复
    HBuilder X实现tabBar底部导航记录
    C/C++内存管理(一)---->new和delete
    安科瑞预付费平台在转供电的应用-Susie 周
    基于图关系归纳偏差的小样本交通预测
    Qt基础之四十六:Qt界面中嵌入第三方程序的一点心得
  • 原文地址:https://blog.csdn.net/weixin_43900869/article/details/133689357