数位DP实战:从蓝桥杯真题掌握二进制问题与通用解法

发布时间:2026/8/29 20:40:58

数位DP实战:从蓝桥杯真题掌握二进制问题与通用解法
1. 项目概述从一道国赛真题看数位DP的精髓最近在复盘蓝桥杯的历年国赛真题第十二届那道“二进制问题”让我印象很深。它初看像是一道普通的组合数学题但数据范围一出来N最大到10^18暴力枚举的路就被彻底堵死了。这正是数位动态规划Digit DP的典型用武之地。很多刚接触数位DP的朋友会觉得它概念抽象、状态设计绕其实它的核心思想非常直观把一个大数的处理转化为对其每一位数字的、带有约束条件的“计数”过程。这道题就是一个绝佳的学习样本它不涉及复杂的进制转换技巧直指数位DP最本质的“数位限制”与“状态记忆”思想。通过拆解这道题我们不仅能学会如何解决“在[1, N]区间内二进制表示中恰好有K个1的数字个数”这一问题更能掌握数位DP的通用解题框架与状态设计心法这种思路同样适用于十进制或其他进制下关于数字特性的统计问题。无论你是正在备赛蓝桥杯还是想深入理解动态规划的一个细分领域这篇从实战出发的拆解都值得一看。2. 问题核心与暴力解法的局限题目描述通常很简洁给定一个正整数NN ≤ 10^18和一个整数KK ≥ 0求[1, N]区间内有多少个正整数的二进制表示中恰好包含K个‘1’。举个例子N13二进制1101K2。那么在1到13之间二进制表示恰好有2个1的数字有3011、5101、6110、91001、101010、121100。一共6个。最直接的想法是暴力循环。从1遍历到N对每个数转换成二进制字符串数其中‘1’的个数如果等于K则计数器加一。这个思路清晰易懂写起来也就十来行代码。但为什么行不通呢关键在于数据规模N最大可达10^18。这是一个天文数字即使每秒能处理1亿个数这已经是极高的性能要求遍历完也需要超过300年。更不用说每个数还要进行进制转换和字符统计开销巨大。因此暴力法在竞赛中毫无悬念会超时Time Limit Exceeded。这迫使我们寻找更聪明的办法。我们注意到数字的二进制表示天然具有“数位”结构。统计满足条件的数字个数实质上是在所有可能的、不超过N的二进制串中筛选出那些‘1’的个数等于K的串。这引导我们想到按位数位进行决策并利用动态规划来高效地统计所有可能情况——这就是数位DP。注意数位DP的“数位”并不特指十进制。任何进制下的每一位都可以视为“数位”二进制只是其中最简单的一种因为每位只有0和1两种选择这反而降低了状态设计的复杂度更适合初学者理解。3. 数位DP通用框架与状态设计心法数位DP通常采用记忆化搜索Memoization Search的实现方式它比递推更直观也更容易处理“上界限制”。其核心是定义一个递归函数dfs(pos, count, limit)并配合一个记忆化数组dp[pos][count]来避免重复计算。我们来拆解这个函数每个参数的含义pos(当前处理位)表示我们正在从高位到低位处理这个二进制数的第几位。通常我们会把数字转换成字符串或数组来处理pos就是当前下标。count(关键状态)表示在已经处理完的高位中我们已经放置了多少个‘1’。这是题目要求恰好K个1的核心状态也是我们记忆化的依据。limit(上限限制)这是一个布尔值表示当前位是否受到原始数字N的对应位的限制。如果limit为真那么当前位能填的最大数字不能超过N在pos位上的值如果为假那么当前位可以填0或1在二进制中。dp[pos][count]数组用于记忆化。它表示在位置pos已经使用了count个‘1且**没有上限限制**limitfalse的情况下从这一位开始往后构造数字所有可能的结果数。为什么要求limitfalse因为limittrue的情况是与特定的N绑定的不具有通用性无法被复用。只有无限制的状态才能被记忆化。状态设计的心法数位DP的状态设计本质上是将那些会影响后续决策的、且与具体数字无关的条件抽象出来。在这道题里唯一影响后续决策的就是“已经用了多少个1”count因为它决定了我们后续还能用多少个1来满足总数K的要求。而“是否紧贴上界”limit是一个过程变量它决定了当前位的选择范围但它本身是“一次性”的所以不作为记忆化数组的维度而是作为递归函数的一个参数来传递。理解了框架我们来看如何将其应用到二进制问题上。4. 针对“二进制问题”的DP状态与转移方程对于本题我们定义记忆化数组dp[pos][cnt]表示当处理到第pos位时前面已经使用了cnt个‘1’并且当前位没有受到N的限制即limitfalse时从这一位往后能构造出的、所有合法数字的个数。这里“合法”最终意味着整个数字的‘1’的总数等于K。递归函数dfs(pos, cnt, limit)的流程如下递归边界如果pos已经超过了数字的最高位即所有位都处理完毕那么我们就得到了一个完整的数字。此时判断已使用的‘1’的数量cnt是否等于K。如果相等则这是一个有效数字返回1否则返回0。记忆化查询如果limit为false即当前位无限制并且dp[pos][cnt]已经被计算过不等于初始值-1那么我们可以直接返回这个缓存的结果。这是提升效率的关键。确定当前位上限计算当前位可以填的最大数字up。如果limit为true则up等于N在pos位上的值0或1如果为false则up为1二进制下最大数字。枚举与决策初始化一个结果变量res 0。然后枚举当前位i从0到up。如果i 0那么‘1’的计数cnt保持不变。如果i 1那么新的计数为cnt 1。我们需要判断新的cnt是否已经超过了K。如果超过了那么后续无论怎么填总‘1’数都会超过K可以直接跳过剪枝因为继续递归没有意义。计算新的limit状态next_limit limit (i up)。这意味着只有当前位是紧贴上界的limittrue并且我这一位填的正是上界值up时下一位才会继续受到限制否则下一位就自由了limitfalse。递归与汇总对于每一个合法的i递归调用dfs(pos1, new_cnt, next_limit)将结果累加到res中。记忆化存储在递归返回前如果当前是limitfalse的无限制状态将res存入dp[pos][cnt]以便后续复用。返回结果返回res。主函数中我们将数字N转换为二进制字符串或数组然后初始化dp数组为-1表示未计算调用dfs(0, 0, true)。注意初始状态从第0位最高位开始已用‘1’数为0并且初始状态是受到限制的limittrue因为我们构造的数字不能超过N。实操心得二进制转换时注意处理前导零。但在本题的dfs过程中我们是从最高有效位开始处理的前导零自然被忽略因为高位为0不影响数值且我们统计的是1的个数。这是二进制数位DP比十进制简单的一个地方十进制处理前导零有时需要额外状态。5. 完整代码实现与逐行解析下面我们用C来实现上述思路的代码并加上详细注释。选择C是因为它是算法竞赛的主流语言执行效率高。#include iostream #include cstring #include vector using namespace std; long long N; int K; long long dp[70][70]; // dp[pos][cnt] 记忆化数组 vectorint digits; // 存放N的二进制每一位从高位到低位 /** * 数位DP记忆化搜索函数 * param pos 当前处理到的位数从0开始指向digits数组 * param cnt 当前已经使用的‘1’的个数 * param limit 当前位是否受到上界N的限制 * return 从当前状态开始能构造出的合法数字个数 */ long long dfs(int pos, int cnt, bool limit) { // 1. 递归边界所有位都处理完了 if (pos digits.size()) { // 判断是否恰好用了K个‘1’ return cnt K ? 1 : 0; } // 2. 记忆化查询只有在无限制状态下才能复用 if (!limit dp[pos][cnt] ! -1) { return dp[pos][cnt]; } // 3. 确定当前位能填的数字上限 int up limit ? digits[pos] : 1; // 二进制每位最大是1 long long res 0; // 4. 枚举当前位的所有可能选择 for (int i 0; i up; i) { int new_cnt cnt (i 1); // 如果当前位填1则计数加1 // 重要剪枝如果已经用的1超过了K后续无论如何都会超过K直接跳过 if (new_cnt K) { continue; } // 计算下一位是否受限当前受限且填到了上限则下一位继续受限 bool next_limit limit (i up); res dfs(pos 1, new_cnt, next_limit); } // 5. 记忆化存储只存储无限制状态的结果 if (!limit) { dp[pos][cnt] res; } return res; } /** * 主求解函数计算[1, N]中二进制表示恰好有K个1的数字个数 * param n 上界N * param k 目标1的个数K * return 满足条件的数字个数 */ long long solve(long long n, int k) { N n; K k; // 将N转换为二进制位数组高位在前 digits.clear(); while (n 0) { digits.push_back(n % 2); n / 2; } reverse(digits.begin(), digits.end()); // 反转使得digits[0]是最高位 // 初始化记忆化数组为-1表示未计算 memset(dp, -1, sizeof(dp)); // 注意dfs从最高位开始初始已用cnt0且初始状态是受限的limittrue return dfs(0, 0, true); } int main() { long long n; int k; // 假设输入为 N 和 K cin n k; cout solve(n, k) endl; return 0; }关键点解析与避坑指南dp数组大小dp[70][70]为什么是70因为N最大为10^18其二进制位数最多不超过60位2^60 ≈ 1.15e18。这里取70是为了留有余地防止边界错误。cnt的维度也是70因为最多不会超过二进制位数。digits数组的顺序我们通过while循环得到的是N的二进制表示的低位在前。但数位DP习惯从高位向低位处理所以必须用reverse反转一下。这是很容易出错的一步。记忆化的条件!limit这是数位DP记忆化的精髓也是新手最容易混淆的地方。一定要理解dp[pos][cnt]定义的是无限制状态下的结果。因为limittrue意味着前面所有位都紧贴N的上界这个状态是“唯一”的依赖于具体的N不能被其他搜索路径复用。只有脱离了上界限制后面的选择才是“自由”的状态才可以被共享。剪枝优化if (new_cnt K)这是一个非常重要的优化。如果在某一位放置1后导致已用1的个数超过了K那么之后无论怎么填即使全填0总1的个数也必然超过K不可能满足条件。因此可以直接跳过这个分支不再进行无谓的递归。这能显著减少搜索空间。初始调用与结果dfs(0, 0, true)的初始limit是true因为我们从构造数字的一开始就不能超过N。这个函数返回的结果就是区间[1, N]内所有满足条件的数的个数。6. 从二进制到通用数位DP的思维扩展通过这道二进制问题我们掌握了数位DP的基本套路。这个套路具有很强的扩展性可以解决一大类“统计区间内满足某种数位特性的数字个数”的问题。关键在于状态设计的灵活变化。状态设计扩展举例十进制下数字和问题求[L, R]内各位数字之和为S的数字个数。状态变化dp[pos][sum]sum表示已处理位数字之和。转移枚举当前位i从0到9受limit限制new_sum sum i。边界pos结束时判断sum S。包含特定数字或模式求[L, R]内不包含数字‘4’的数字个数。状态变化可以不需要额外的计数状态但需要一个标志位hasFour。更通用的做法是如果限制条件更复杂如不能连续出现两个‘6’状态就需要包含前一位的信息例如dp[pos][pre]pre表示前一位填的数字。模运算相关求[L, R]内能被M整除的数字个数。状态变化dp[pos][mod]mod表示当前构造的数字对M取模的结果。转移new_mod (mod * 10 i) % M十进制下。边界pos结束时判断mod 0。通用解题步骤总结问题转化将区间[L, R]的问题转化为[1, R]的结果减去[1, L-1]的结果前缀和思想。数位化将上界数字转换为数位数组如字符串、vector。设计状态分析满足题目条件需要记录哪些与具体数字无关的、影响后续决策的信息。常见的有计数如1的个数、数字和、模数、前导零标志、前一位数字等。定义DP数组dp[pos][state1][state2]...通常不包含limit维度。编写DFS函数参数(pos, state..., limit)边界返回条件判断。记忆化if (!limit dp[pos][state] ! -1) return ...枚举当前位计算新状态进行剪枝如果可能。递归累加结果。记忆化存储在!limit条件下。初始化与调用初始化DP数组为-1调用dfs(0, init_state, true)。7. 常见问题与调试技巧实录在实际编写和调试数位DP时以下几个坑点我几乎每次都会提醒自己注意Q1结果总是偏大或偏小检查点1limit的记忆化条件。这是最最常见的错误。确保只在!limit时才读取和存储dp数组。如果你错误地把limittrue的状态也存了会导致结果重复计算因为不同的受限路径可能对应同一个(pos, cnt)状态但它们后续的选择空间其实是不同的。检查点2递归边界返回值。仔细核对pos digits.size()时返回的条件。本题是cnt K ? 1 : 0。如果是求数字和可能就是sum S ? 1 : 0。返回错误会导致基础计数单元出错。检查点3digits数组的生成。确认是从最高位到最低位存储的吗reverse了吗处理N0的情况了吗本题区间是[1,N]所以N0时直接返回0但digits数组会为空DFS边界需要能正确处理。Q2程序运行超时即使用了记忆化排查点1状态设计是否合理状态数量是pos数 × 状态空间大小。如果状态空间太大例如你设计了一个dp[pos][sum][mod][pre]每个维度范围都很大那么记忆化也救不了。需要思考状态能否合并或简化。排查点2剪枝是否充分像本题中的if (new_cnt K) continue;就是很好的剪枝。在其他问题中也要积极寻找类似的“提前终止无效搜索”的条件。排查点3dp数组初始化开销。每次调用solve都memset整个dp数组如果dp很大比如dp[20][200][200]会有一定开销。在多次查询不同[L,R]区间的问题中可以考虑复用或更精细的初始化。Q3如何处理前导零情况分析在统计数字本身属性如1的个数、数字和时前导零不影响结果通常无需特殊处理就像本题一样。需要处理的情况当题目条件与前导零有关时例如“统计不含前导零的、各位数字互不相同的数字”。这时需要在状态中增加一个isLead参数表示当前是否还处于前导零阶段。在isLeadtrue且当前位填0时isLead保持为true且cnt等状态不更新因为前导零不计入统计。调试技巧小数据对拍写一个暴力程序用于N较小比如N10000的情况。用你的数位DP程序与暴力程序的结果进行对比快速定位错误。打印递归树在DFS函数入口打印pos, cnt, limit和当前位选择i可以非常清晰地看到程序的执行路径帮助你理解limit是如何传递和变化的以及记忆化是否生效。关注边界值测试N0,N1,K0,K1以及K大于二进制位数的情况。这些边界情况最容易出问题。最后数位DP的熟练离不开练习。理解了二进制这个简单模型后可以尝试蓝桥杯或其他OJ上的十进制数位DP题目例如“不要62”、“windy数”等经典问题逐步加深对状态设计和问题转化的理解。这道“二进制问题”就像一把钥匙帮你打开了数位DP这扇门门后的世界还需要你用自己的代码去探索和征服。

相关新闻

数学建模竞赛实战:从数据处理到算法选型的完整技术链路解析

数学建模竞赛实战:从数据处理到算法选型的完整技术链路解析

2026/8/29 20:40:58

1. 从“思路点播”到“实战复盘”:我们如何拆解一道数学建模赛题又到了一年一度的数学建模竞赛季,后台和社群里关于“华中杯A题”的讨论又多了起来。看到“思路点播”这个词,我特别有感触。很多同学在备赛时,总希望拿到一份“标准…

YOLO小数据集实战:多场景坐姿检测从数据预处理到模型部署

YOLO小数据集实战:多场景坐姿检测从数据预处理到模型部署

2026/8/29 20:40:58

简介:目标检测是计算机视觉的核心任务之一,旨在从图像中定位并识别出特定物体。其原理通常基于深度学习模型,通过卷积神经网络提取特征,并利用回归和分类头输出目标的边界框与类别。这项技术的价值在于能将视觉信息转化为结构化数…

51单片机定时器与计数器核心原理、四种工作模式详解与实战应用

51单片机定时器与计数器核心原理、四种工作模式详解与实战应用

2026/8/29 20:30:57

1. 项目概述:为什么51单片机的定时器和计数器是核心基本功? 搞过51单片机的朋友都知道,无论你是做智能小车、万年历、风扇摇头还是简单的流水灯,几乎都绕不开两个东西:定时器和计数器。这俩功能模块就像是单片机的“心…

前端面试八股文拆解:吃透事件循环、闭包与原型链

前端面试八股文拆解:吃透事件循环、闭包与原型链

2026/8/29 22:51:04

前端求职圈的“八股文”,已经成了绕不开的话题。每年金三银四、金九银十,各大技术群里讨论最激烈的,永远不是某个新框架的源码,而是“闭包是什么”“事件循环怎么回事”“浏览器从输入URL到页面展示经历了什么”这类看起来基础到不…

手写 new、bind、call、apply:彻底搞懂 JavaScript 函数调用机制与 this 指向

手写 new、bind、call、apply:彻底搞懂 JavaScript 函数调用机制与 this 指向

2026/8/29 22:51:04

前端面试,无论是校招还是社招,几乎都会有一道“手写题”环节。而手写 new、bind、call、apply 这四个方法,是我见过出现频率最高的一组,甚至可以说是前端八股文里的“钉子户”。我最早看到这个题目时只觉得无聊,毕竟日…

微博情感分析毕业设计全流程指南:从数据采集到模型部署

微博情感分析毕业设计全流程指南:从数据采集到模型部署

2026/8/29 22:51:04

简介:情感分析是自然语言处理领域的核心任务之一,旨在通过计算模型自动识别文本中蕴含的情感倾向。其基本原理是将文本转化为机器可理解的特征表示,再通过分类算法判断情感极性。这项技术在商业智能、舆情监控、产品反馈分析等场景中具有重要…

前端工具函数实战:深拷贝、防抖节流、发布订阅与懒加载全解析

前端工具函数实战:深拷贝、防抖节流、发布订阅与懒加载全解析

2026/8/29 22:51:04

前几年团队招人,我面试过不少前端候选人,聊到“手写工具函数”这一环,十个人里有七八个都能把防抖、节流背得滚瓜烂熟,代码也写得像模像样。但一问到“防抖和节流分别解决什么场景问题”“深拷贝遇到循环引用怎么处理”“发布订阅…

前端必备五大利器:深拷贝、发布订阅、节流防抖与懒加载

前端必备五大利器:深拷贝、发布订阅、节流防抖与懒加载

2026/8/29 22:51:04

深拷贝、发布订阅、节流、防抖、懒加载——这五个工具函数,前端面试八股文里的钉子户,也是你日常开发中几乎每天都要打交道的基础设施。我见过太多人背了答案却写不出代码,或者写出来能跑但一碰到边界情况就翻车。这篇文章不止是把这五个函数…

OmniRoute技术栈鸟瞰:Next.js+open-sse如何撑起450+贡献者的免费AI网关

OmniRoute技术栈鸟瞰:Next.js+open-sse如何撑起450+贡献者的免费AI网关

2026/8/29 22:41:03

OmniRoute技术栈鸟瞰:Next.jsopen-sse如何撑起450贡献者的免费AI网关 【免费下载链接】OmniRoute Never stop coding. Free MIT AI gateway: one endpoint, 350 providers (90 free), 1200 models Kimi, Claude, GPT, Gemini, GLM, DeepSeek, MiniMax. Works with C…

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

2026/8/27 11:10:02

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

2026/8/29 10:22:10

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

2026/8/28 7:34:42

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

四款热门降AI工具测评:研究生和本科生怎么选?

四款热门降AI工具测评:研究生和本科生怎么选?

2026/8/29 0:09:39

马上要交论文了,最近真的被论文ai率折磨的够呛。 明明查重都没问题了,但是ai率就是居高不下,崩溃了,明明都是我自己写的,天杀的,明明都是我亲生的啊 改来改去,终于给我搞出一套完美的降ai方案…

论文降AI率免费攻略:自查、提示词与工具推荐

论文降AI率免费攻略:自查、提示词与工具推荐

2026/8/29 0:09:39

马上要交论文了,最近真的被论文ai率折磨的够呛。 明明查重都没问题了,但是ai率就是居高不下,崩溃了,明明都是我自己写的,天杀的,明明都是我亲生的啊 改来改去,终于给我搞出一套完美的降ai方案…

北京GEO优化服务商推荐:预算型企业如何选北京GEO优化服务商?

北京GEO优化服务商推荐:预算型企业如何选北京GEO优化服务商?

2026/8/29 0:09:39

前言:预算有限的企业更关心投入能否形成可持续的品牌资产。评估北京GEO优化服务商时,不能只比较单篇内容或单月报价,还要看是否能够把问题词、官网、信源和监测串成完整链路。本期重点放在预算配置、试点范围和交付边界,帮助企业先…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/28 7:35:26

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/28 7:34:51

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/28 7:34:35

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…