当前位置: 首页 > news >正文

数位DP

数位dp

我从就只用记忆化写数位dp, 随便写写, 因为某天突然不会写了

首先数位dp一般会把 \([l,r]\) 拆成 \([1,r]\)\([1,l-1]\) , 因为不管你问同一个数什么问题, 它的答案一定都是一样的, 我们之后就不提这个事了

怎么dp? 我不会, 我不会, 我不会

但是搜索简单, 我们写记忆化(致敬我有关变量在外边的错误记忆化)

我们那一道题做例子


P4999 烦人的数学作业

题意: [l,r] 每个数的数位和

正常的爆搜, 一位一位填数谁都会, 我们考虑如何把两个一模一样的结构给挖出来, 也就是我们需要记录什么

首先是解决问题必须的, 当前填到哪一位, 数位到现在加到多少

其次我们考虑对于一个情况, 怎么保证搜索树中一个情况的子树完全相同

我们在处理 [1,114514] 时, 假设我们在添第四位, 如果第三为添的是 \(4\) , 我们在第四位便不可以添超过 \(5\) 的数字, 若没有则可以从 \(0\) 填到 \(9\) , 我们需要记录当前位置有没有限制, 这个是所有数位dp都需要的

关于前导零, 这个问题求的是和, 所以不用考虑, 但做题的时候记得研究会不会造成不同

这样我们就在搜索树中, 一模一样的子树我们只需要计算一次, 大大加速了我们的算法

具体到代码中看一看吧


\(dfs\) 函数

int dfs(int pos, bool limit, int sum){if(!pos) return sum;//如果填完就返回当前值if(!limit&&dp[pos][sum]) return dp[pos][sum]; //若有搜索过一模一样的返回搜过的值int up=(limit?a[pos]:9), res=0;//确定枚举填数的上限for(int i=0; i<=up; ++i){res=(res+dfs(pos-1,i==up&&limit, sum+i))%mod;//直接继续, limit和sum继承当前}if(!limit) dp[pos][sum]=res%mod;//保存子树return res%mod;//返回
}

其实我们同样可以将我们的 \(limit\) 记作 \(dp\) 中的一维, 只是记录没什么大用罢了


之后放一下整体代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
int dp[20][10000];
const int mod=1e9+7;
int a[20], len;
int dfs(int pos, bool limit, int sum){if(!pos) return sum;if(!limit&&dp[pos][sum]) return dp[pos][sum];int up=(limit?a[pos]:9), res=0;for(int i=0; i<=up; ++i){res=(res+dfs(pos-1,i==up&&limit, sum+i))%mod;}if(!limit) dp[pos][sum]=res%mod;return res%mod;
}
int solve(int x){len=0;while(x>0){//分解数字a[++len]=x%10;x/=10;}return dfs(len,true,0)%mod;
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int T; cin>>T;while(T--){int l, r; cin>>l>>r;cout<<(solve(r)-solve(l-1)+mod)%mod<<'\n';}return 0;
}

P2602 数字计数

给定两个正整数 ab,求在 [a,b] 中的所有整数中,每个数码(digit)各出现了多少次。

这个需要考虑前导零了, 填到 00_ 的时候如果填 \(0\) 总不能记一个贡献吧

其实记搜写这个都大差不差

\(dfs\) 函数

int dfs(int pos, bool limit, bool lead, int sum){if(!pos) return sum;if(!limit&&!lead&&dp[pos][sum][digit!=0]!=-1) return dp[pos][sum][digit!=0];//0与其他不同,受到前导零的影响int res=0, up=limit?a[pos]:9;for(int i=0; i<=up; ++i){int tmp=sum+(i==digit);if(lead&&digit==0&&i==0) tmp=0; //就是刚刚提到的情况res+=dfs(pos-1,limit&&i==up,lead&&i==0,tmp);}if(!limit&&!lead) dp[pos][sum][digit!=0]=res;return res;
}

想必也容易看懂


直接放代码↓

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MN=100;
int dp[MN][MN*MN][2];
int a[MN], len, digit;
int dfs(int pos, bool limit, bool lead, int sum){if(!pos) return sum;if(!limit&&!lead&&dp[pos][sum][digit!=0]!=-1) return dp[pos][sum][digit!=0];int res=0, up=limit?a[pos]:9;for(int i=0; i<=up; ++i){int tmp=sum+(i==digit);if(lead&&digit==0&&i==0) tmp=0;res+=dfs(pos-1,limit&&i==up,lead&&i==0,tmp);}if(!limit&&!lead) dp[pos][sum][digit!=0]=res;return res;
}
int cnt[10];
void solve(int x, int opt){len=0; while(x){a[++len]=x%10; x/=10;}for(int i=0; i<=9; ++i){digit=i; int res=dfs(len,1,1,0);cnt[i]+=opt*res;}
}
int l, r;
signed main(){memset(dp,-1,sizeof(dp));ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>l>>r;solve(r,1); solve(l-1,-1);for(int i=0; i<=9; ++i) cout<<cnt[i]<<" ";return 0;
}

用记忆化写数位dp, 我们可以直接套用这个模式走

dfs函数:

  • 填完返回贡献
  • 返回相同情况
  • 确定枚举上限
  • 进行下一步搜索
  • 记录当前情况的值

所以这个没什么需要讲的, 真正搞明白一个, 其他的就很好懂了

下边放点题, 没啥好说的, 就说说dfs的细节, 然后给代码了


题目

P6218 # [USACO06NOV] Round Numbers S

如果一个正整数的二进制表示中,0 的数目不小于 1 的数目,那么它就被称为「圆数」。

例如,9 的二进制表示为 1001,其中有 2021。因此,9 是一个「圆数」。

请你计算,区间 [l,r] 中有多少个「圆数」。

我们按照二进制拆数, 肯定需要考虑前导零, 记录 \(0\)\(1\) 的数量, 直接套板子

代码↓

#include <bits/stdc++.h>
using namespace std;
const int MN=1e2+114;
int dp[MN][MN][MN], a[MN];
int dfs(int pos, bool limit, bool lead, int zero, int one){if(!pos) return zero>=one;if(!lead&&!limit&&dp[pos][zero][one]) return dp[pos][zero][one];int res=0, up=limit?a[pos]:1;for(int i=0; i<=up; ++i){if(lead&&i==0)res+=dfs(pos-1,limit&&i==up,1,zero,one);elseres+=dfs(pos-1,limit&&i==up,0,zero+(i==0),one+(i==1));}if(!lead&&!limit) dp[pos][zero][one]=res;return res;
}
int solve(int x){int len=0;while(x){a[++len]=x%2;x/=2;}return dfs(len,1,1,0,0);
}
int main(){int l, r;cin>>l>>r;cout<<solve(r)-solve(l-1);return 0;
}

P2657 Windy数

不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。windy 想知道,在 ab 之间,包括 ab ,总共有多少个 windy 数?

每一次填数合法仅与上一个数有关, 记录上一个数字, 考虑前导零, 套板子

代码↓

#include <iostream>
#define int long long
using namespace std;
int A, B; const int MN=40;
int a[MN];
int dp[MN][MN];
int dfs(int pos, bool limit, bool lead, int last){if(!pos) return 1;if(!limit&&!lead&&dp[pos][last]) return dp[pos][last];int up=limit?a[pos]:9, res=0;for(int i=0; i<=up; ++i){if(lead&&i==0) res+=dfs(pos-1,limit&&i==up, 1, 0);else if(lead||abs(last-i)>=2) res+=dfs(pos-1,limit&&i==up, 0, i);}if(!limit&&!lead) dp[pos][last]=res;return res;
}
int solve(int x){int len=0;while(x){a[++len]=x%10; x/=10;}return dfs(len,1,1,0);
}
signed main(){ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>A>>B;cout<<solve(B)-solve(A-1); return 0;
}
http://www.hskmm.com/?act=detail&tid=24748

相关文章:

  • 2025橡胶软接头厂家最新企业品牌推荐排行榜,法兰橡胶软接头,可曲挠,挠性,KXT,耐油,EPDM,耐腐蚀,三元乙丙橡胶软接头,橡胶柔性软接头公司推荐!
  • 整体二分笔记
  • 详细介绍:性能优化 - 案例篇:缓存_Guava#LoadingCache设计
  • 2025年X射线管厂家最新企业品牌推荐排行榜,工业用金属陶瓷,波长色散荧光分析,应力衍射分析,管板角焊缝,轮胎检测,辐照,固定阳极波纹陶瓷,测厚,食品检测 X 射线管公司推荐
  • AtCoder Beginner Contest 400
  • P14134 题解——随机化太帅了
  • 2025 年北京档案存放公司 升职猫档案服务平台:16 年老牌机构的合规服务与高效解决方案解析
  • 2025电容厂家最新品牌推荐排行榜白皮书,固态,高压,牛角,安规,CBB,超级,红宝石电解,螺栓,超级电容推荐这十家公司!
  • bug汇总
  • 2025石材加工厂家最新品牌推荐排行榜:大祥工艺,业务覆盖东北,辽宁盖州,专业浮雕雕刻高级技师
  • centos7升级降级内核 centos升级降级内核 centos升级内核 centos降级内核
  • Photoshop 在线网页版?是的,它来了!免费使用指南
  • 基于Python+Vue开发的母婴商城管理系统源码+运行步骤
  • 2025防火隔断品牌最新推荐排行榜:甲级防火玻璃隔断厂家深度解析,精选优质品牌助力采购决策!
  • 机器学习Day5-模型诊断 - 详解
  • 这些行业软件你用过哪个
  • 提供远程服务
  • 分享一些软件资讯
  • 2025美缝剂源头厂家最新推荐权威排行榜:深度解析五大品牌实力与选购指南
  • 2025数控铣床厂家最新企业品牌推荐排行榜, 双头数控铣床,双面数控铣床,龙门数控铣床,双侧数控铣床推荐这十家公司!
  • 2025最新推荐点胶机源头厂家权威排行榜:涵盖自动点胶机,果冻胶,无痕内衣,热熔胶类设备,助力企业精准挑选优质厂家
  • 前端开源JavaScrip库 - 详解
  • Probabilistic method小记
  • 数据生成方法初步调研
  • Elastic Stack 9.1.4 发布:重要安全更新与功能优化
  • 2025钛白粉源头厂家最新推荐排行榜:覆盖广东珠三角东莞华南深圳长三角地区的优质供应商解析
  • 完整教程:图论回溯
  • 用 Whisper 打破沉默:AI 语音技术如何重塑无障碍沟通方式? - 指南
  • 做题记录(Oct.)
  • 生成式AI改进极端多标签分类技术