(蓝桥杯)二进制问题(数位DP)

这其实就是一道基础的数位DP题,如果对这方面知识有了解的同学可能就能直接做出来,如果不知道什么是数位DP的同学可以看这里:数位DP模板详解_AC__dream的博客-优快云博客  

显然我们f数组中只需要存下当前位pos以及遍历到当前位有多少个1,由于这道题的结果是与0无关的,所以也就不需要考虑前导0了,直接当pos位变为0时返回就行了,如果遍历到最后一位当前1的个数是k个就返回1,否则返回0,在过程中如果出现1的次数大于k可以直接剪掉。

别的就没什么好说的了,下面是代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=63;
long long f[N][N];
int a[N];
int k;
long long dfs(int pos,int cnt,int limit)
{
	if(!pos) return cnt==k;
	if(!limit&&f[pos][cnt]!=-1) return f[pos][cnt];
	int up=limit?a[pos]:1;
	long long ans=0;
	for(int i=0;i<=up;i++)
		ans+=dfs(pos-1,cnt+(i==1),limit&&(i==up));
	if(!limit) f[pos][cnt]=ans;
	return ans;
}
long long solve(long long x)
{
	int pos=0;
	memset(f,-1,sizeof f);
	while(x)
	{
		a[++pos]=x%2;
		x/=2;
	}
	return dfs(pos,0,1);
}
int main()
{
	long long n;
	cin>>n>>k;
	cout<<solve(n);
	return 0;
}

<think>好的,我现在需要回答用户关于数位DP问题,包括它的概念、实现方法和应用。首先,我需要回忆数位DP的基本定义和核心思想。根据用户提供的引用[1],数位DP是专注于数字各个数位动态规划类型,常用于解决与数位相关的问题,比如统计满足特定条件的数字数量。比如统计区间内不包含某个数字的数,或者数字各位之和满足条件的数。 接下来,用户问如何实现数位DP。根据已有的知识,数位DP通常涉及将数字分解成各位,然后按位进行状态转移。实现时需要考虑记忆化搜索,避免重复计算。可能需要预处理数位,处理每一位的可能取值,尤其是前导零和上限的问题。比如,当处理数字123时,第一位如果是0,后面的位可以自由选择,但如果是1,第二位可能受限于2,以此类推。需要设计状态,比如当前位数、是否受限、前导零状态以及其他需要的参数,如各位和等。 关于应用场景,引用[1]提到在蓝桥杯等竞赛中常见,比如统计区间内满足某种数位条件的数字个数,比如windy数、不含4或62的数字等。应用时需要将具体问题转化为状态转移的条件,设计合适的状态和转移方程。 需要确保回答的结构清晰,分点说明概念、实现步骤和应用例子。同时,要检查是否引用了提供的参考文献,比如在概念部分引用[1],在动态规划通用方法部分引用[2]。 另外,用户提供的引用[2]提到动态规划的最优子结构和无后效性,这些在数位DP中同样适用。需要指出数位DP满足这些性质,状态设计时要保证无后效性,比如一旦处理到某一位,后续的状态只依赖于当前位的状态,而不关心之前如何到达这里。 现在需要组织这些内容,分概念、实现、应用三个部分。可能还需要示例问题,比如计算[L, R]区间内满足各位数字之和为特定值的数的个数,用来说明实现步骤。 需要注意使用中文回答,数学公式用$...$或$$...$$格式,代码块正确使用语法。可能还需要生成相关问题,比如数位DP的经典例题,如何优化数位DP的状态设计,数位DP中的记忆化技巧等。 最后,确保引用的标注正确,比如在提到数位DP概念时添加[^1],在动态规划基本原则时添加[^2]。</think>数位动态规划数位DP)是一种针对数字各个数位设计的动态规划方法,常用于解决与数字数位相关的计数或条件判断问题。例如统计区间$[L,R]$内满足“各位数字不含4”或“各位和等于特定值”的数字个数。 ### 一、数位DP的核心概念 1. **数位拆分**:将数字按十进制逐位分解,例如$123$分解为$[1,2,3]$。 2. **状态设计**:通常包含当前位数、前导零状态、数位限制(是否达到上限)以及问题相关的参数(如各位和、特定数字标记等)。 3. **记忆化搜索**:通过缓存子问题结果避免重复计算,例如用$dp[pos][sum][limit]$记录处理到第$pos$位、当前和为$sum$、是否受数位限制时的结果[^2]。 ### 二、实现步骤(以统计$[0,N]$中各位和为$T$的数字数量为例) 1. **数位拆分**:将数字$N$转换为字符数组或列表,如`digits = list(str(N))`。 2. **状态定义**:设计动态规划状态,例如: $$dp[pos][sum][is\_limit] = \text{第pos位时和为sum且是否受限制的数字数量}$$ 3. **递归搜索**:按位枚举可能的值,处理前导零和数位限制。 ```python def count_numbers_with_sum(T, N): digits = list(map(int, str(N))) n = len(digits) @lru_cache(maxsize=None) def dfs(pos, current_sum, is_limit): if pos == n: return 1 if current_sum == T else 0 limit = digits[pos] if is_limit else 9 total = 0 for d in range(0, limit + 1): new_limit = is_limit and (d == limit) total += dfs(pos + 1, current_sum + d, new_limit) return total return dfs(0, 0, True) ``` ### 三、典型应用场景 1. **统计区间内不含特定数字的数**:如蓝桥杯“不包含4或62的数字”[^1]。 2. **计算满足数位和条件的数**:如求$[L,R]$中各位和为素数的数的数量。 3. **windy数问题**:相邻数位差的绝对值至少为2的数的计数。 ### 四、关键优化点 1. **状态压缩**:合并冗余状态(如前导零标记`lead`)。 2. **剪枝策略**:提前终止不可能达到目标的分支(如当前和已超过目标值)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值