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

2023-10-09 07:20

本文主要是介绍【每日一题】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 <bits/stdc++.h>
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 位,要选择这么些个 1int 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;
}

这篇关于【每日一题】AcWing 5271. 易变数 | 思维 | 中等的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/171241

相关文章

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

每日一练7:简写单词(含链接)

1.链接 简写单词_牛客题霸_牛客网 2.题目 3.代码1(错误经验) #include <iostream>#include <string>using namespace std;int main() {string s;string ret;int count = 0;while(cin >> s)for(auto a : s){if(count == 0){if( a <=

【每日刷题】Day113

【每日刷题】Day113 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 91. 解码方法 - 力扣(LeetCode) 2. LCR 098. 不同路径 - 力扣(LeetCode) 3. 63. 不同路径 II - 力扣(LeetCode) 1. 91. 解码方法 - 力扣(LeetCode) //思路:动态规划。 cl

【AcWing】851. 求最短路

spfa算法其实是对贝尔曼福特算法做一个优化。 贝尔曼福特算法会遍历所有边来更新,但是每一次迭代的话我不一定每条边都会更新,SPFA是对这个做优化。 如果说dist[b]在当前这次迭代想变小的话,那么一定是dist[a]变小了,只有a变小了,a的后继(b)才会变小。 用宽搜来做优化,用一个队列,队列里边存的就是所有变小了的结点(队列里存的是待更新的点)。 基本思路就是我更新过谁,我再拿

颠覆你的开发模式:敏捷思维带来的无限可能

敏捷软件开发作为现代软件工程的重要方法论,强调快速响应变化和持续交付价值。通过灵活的开发模式和高效的团队协作,敏捷方法在应对动态变化和不确定性方面表现出色。本文将结合学习和分析,探讨系统变化对敏捷开发的影响、业务与技术的对齐以及敏捷方法如何在产品开发过程中处理持续变化和迭代。 系统变化对敏捷软件开发的影响 在敏捷软件开发中,系统变化的管理至关重要。系统变化可以是需求的改变、技术的升级、

力扣 739. 每日温度【经典单调栈题目】

1. 题目 理解题意: 1.1. 给一个温度集合, 要返回一个对应长度的结果集合, 这个结果集合里面的元素 i 是 当前 i 位置的元素的下一个更高温度的元素的位置和当前 i 位置的距离之差, 若是当前元素不存在下一个更高温度的元素, 则这个位置用0代替; 2. 思路 本题用单调栈来求解;单调栈就适用于来求当前元素左边或者右边第一个比当前元素大或者小的元素;【单调栈:让栈中的元素保持单调

每日一题——第八十一题

打印如下图案: #include<stdio.h>int main() {int i, j;char ch = 'A';for (i = 1; i < 5; i++, ch++){for (j = 0; j < 5 - i; j++){printf(" ");//控制空格输出}for (j = 1; j < 2 * i; j++)//条件j < 2 * i{printf("%c", ch

每日一题,力扣leetcode Hot100之238.除自身以外数组的乘积

乍一看这个题很简单,但是不能用除法,并且在O(N)时间复杂度完成或许有点难度。 考虑到不能用除法,如果我们要计算输出结果位置i的值,我们就要获取这个位置左边的乘积和右边的乘积,那么我新设立两个数组L和R。 对于L来说,由于表达的是位置i左边的数的乘积,那么L[0]=1,因为第一个数字左边没数那么为了不影响乘积初始值就设置为1,那么L[1]=L[0]*nums[0],那么L[i]=L[i-1

【中等】保研/考研408机试-二分查找(模板题)

二分查找就是在一个有序数组中查找某个值,以首端尾端的中点mid查找对比,mid与要查找的数进行对比,看落在哪个区间,在那个区间重新得到首端和尾端,进而得到新的mid值。 一、模板题 二分查找-I_牛客题霸_牛客网 class Solution {public:int search(vector<int>& nums, int target) {int left=0,right=nums.s