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

【动态规划】数位dp

典中典 P2602 数字计数

递推实现

首先计算0000…~9999…之间数字出现次数

  • 递推角度dp[i]=dp[i−1]×10+10i−1,dp[i]是i位数的计数dp[i]=dp[i-1]\times10+10^{i-1},dp[i]是i位数的计数dp[i]=dp[i1]×10+10i1,dp[i]i位数的计数

    • 10i−110^{i-1}10i1是最高位上出现次数
    • dp[i−1]×10dp[i-1]\times 10dp[i1]×10是其他低位上出现次数
  • 排列组合角度dp[i]=i×10i/10dp[i]=i\times 10^i/10dp[i]=i×10i/10

    • i个0递增到i个9 所有字符出现i×10ii\times 10^ii×10i
    • /10/10/10分配到每个数字上

然后分普通情况和逼近情况计算0~x的计数,使用前缀和

const int N=16,INF=1e9+10;
int cnta[10],cntb[10];
int dp[N],ten[N];
void init(){//000...~999...ten[0]=1;forr(i,1,N-1){dp[i]=i*ten[i-1];ten[i]=ten[i-1]*10;}
}
void calc(int x,int *cnt){int len=0;vector<int>num(15,0);while (x){num[++len]=x%10;x/=10;}reforr(i,1,len){//从高到低位 假设为324//普通情况forr(j,0,9)cnt[j]+=num[i]*dp[i-1];// 0~299 后两位  0 1...num[i]-1forr(j,0,num[i]-1)cnt[j]+=ten[i-1];// 0~299 高位 //逼近情况int tp=0;forr(j,1,i-1)tp+=num[j]*ten[j-1];cnt[num[i]]+=(tp+1); //300~324 0~tp 共tp+1//去除前导零cnt[0]-=ten[i-1];}}
void solve()
{init();int a,b;cin>>a>>b;calc(a-1,cnta),calc(b,cntb);forr(i,0,9)cout<<cntb[i]-cnta[i]<<' ';
}

记忆化搜索实现

const int N=16,INF=1e9+10;
int dp[N][N][2][2];
int now,num[N];
/*
pos:从高到低 处理到几位数字
sum:前面now的个数
lead:有无前导零
limit:有无数位限制
*/
int dfs(int pos,int sum,bool lead,bool limit){int ans=0;if(pos==0)return sum;if(dp[pos][sum][lead][limit]!=-1)return dp[pos][sum][lead][limit];int up=(limit?num[pos]:9);forr(i,0,up){//决定这一位是什么数if(i==0&&lead)ans+=dfs(pos-1,sum,1,limit&&i==up);else if(i!=now)ans+=dfs(pos-1,sum,0,limit&&i==up);//没有前导零else if(i==now)ans+=dfs(pos-1,sum+1,0,limit&&i==up);}dp[pos][sum][lead][limit]=ans;return ans;
}
int calc(int x){int len=0;while (x){num[++len]=x%10;x/=10;}memset(dp,-1,sizeof dp);return dfs(len,0,1,1);//lead=true出0就是前导零了 limit&&k->k
}
void solve()
{int a,b;cin>>a>>b;forr(i,0,9)now=i,cout<<calc(b)-calc(a-1)<<' ';
}

P2657 Windy数

const int N=16,INF=1e9+10;
int dp[N][N][2][2];
int now,num[N];/*
pos:从高到低 处理到几位数字
lst:最后一个数
lead:有无前导零
limit:有无数位限制
*/
int dfs(int pos,int lst,bool lead,bool limit){int ans=0;if(pos==0)return 1;if(dp[pos][lst][lead][limit]!=-1)return dp[pos][lst][lead][limit];int up=(limit?num[pos]:9);forr(i,0,up){//决定这一位是什么数if(abs(i-lst)<2)continue;if(i==0&&lead)ans+=dfs(pos-1,-2,1,limit&&i==up);//有前导零else ans+=dfs(pos-1,i,0,limit&&i==up);//没有前导零}dp[pos][lst][lead][limit]=ans;return ans;
}
int calc(int x){int len=0;while (x){num[++len]=x%10;x/=10;}memset(dp,-1,sizeof dp);return dfs(len,-2,1,1);//-2是为了让最高位是任意数 lead=true出0就是前导零了 limit&&k->k
}
void solve()
{int a,b;cin>>a>>b;cout<<calc(b)-calc(a-1)<<endl;
}

P4124 手机号码

const int N=16,INF=1e9+10;
int dp[N][N][N][2][2][2][2];
int num[N];/*
pos:从高到低 处理到几位数字
sec:倒数第二个数 lst:最后一个数 state:是否出现三连
et:8 fr:4
(不用考虑前导零)
limit:有无数位限制
*/
int dfs(int pos,int sec,int lst,bool state,bool et,bool fr,bool limit){int ans=0;if(et&&fr)return 0;if(pos==0)return state;if(dp[pos][sec][lst][state][et][fr][limit]!=-1)return dp[pos][sec][lst][state][et][fr][limit];int up=(limit?num[pos]:9);forr(i,0,up){//决定这一位是什么数ans+=dfs(pos-1,lst,i,state||(i==lst&&i==sec),et||(i==8),fr||(i==4),limit&&i==up);}dp[pos][sec][lst][state][et][fr][limit]=ans;return ans;
}
int calc(int x){int len=0;while (x){num[++len]=x%10;x/=10;}if(len!=11)return 0;int ans=0;memset(dp,-1,sizeof dp);forr(i,1,num[len])ans+=dfs(len-1,0,i,0,i==8,i==4,i==num[len]);return ans;
}
void solve()
{int a,b;cin>>a>>b;cout<<calc(b)-calc(a-1)<<endl;
}

P2518 计数

题意:重新把所给的数各位排列组合,能得到多少更小的数
思路:可重复康托展开
康托展开速成


const int N=55,mod=1e9+7;
int c[N][N],sn[N],a[12];
int full_perm(int len){//全排列 len:剩下的位数int res=1;forr(i,0,9){res*=c[len][a[i]];len-=a[i];}return res;
}
void solve()
{string s;cin>>s;int len=s.size();forr(i,0,len-1){sn[i+1]=s[i]-'0';a[sn[i+1]]++;}//杨辉三角求组合数c[0][0]=1;forr(i,1,len){c[i][0]=c[i][i]=1;forr(j,1,i-1)c[i][j]=c[i-1][j-1]+c[i-1][j];}//开始展开int ans=0;forr(i,1,len){forr(j,0,sn[i]-1){//枚举比该位上小的数字if(a[j]){a[j]--;//让这一位上是jans+=full_perm(len-i);//求全排列a[j]++;//还回来 继续定下一个数}}a[sn[i]]--;//题意让这一位≤n[i] 那就这一位上定死是n[i] 不然之后在全排列的时候 会重复}cout<<ans<<endl;
}
http://www.lryc.cn/news/606367.html

相关文章:

  • QT收费情况
  • SpringBoot实战:高效Web开发
  • SAM附录详解
  • Android依赖注入框架Hilt入门指南
  • iOS软件性能监控实战指南 开发到上线的完整流程解析
  • 上传文件到服务器
  • C++11特性——右值引用与移动语义
  • 基于大模型的知识库落地实施策略
  • 硬件-音频学习DAY1——音箱材料选择:密度板为何完胜实木
  • opencv解迷宫
  • 图论:SPFA算法
  • 20250731在荣品的PRO-RK3566开发板的Android13下解决敦泰的FT8206触控芯片的只有4点触控功能
  • 经典算法之美:冒泡排序的优雅实现
  • 【计算机网络】IP地址、子网掩码、网关、DNS、IPV6是什么含义?计算机中如何设置子网掩码与网关?
  • 分类-鸢尾花分类
  • 基于SpringBoot和SpringAI框架实践
  • 数据转换能干什么?有哪些好用的数据转换方法?
  • 【React】diff 算法
  • 深度解析领域特定语言(DSL)第七章:语法分析器组合子 - 用乐高思维构建解析器
  • 借助于llm将pdf转化为md文本
  • 循环神经网络RNN原理精讲,详细举例!
  • 【智能体agent】入门之--2.2框架---autoGen
  • Cesium 快速入门(一)快速搭建项目
  • 【05】大恒相机SDK C#开发 —— Winform中采集图像并显示
  • 提示词增强工程(Prompt Enhancement Engineering)白皮书草稿
  • 【大模型理论篇】混合思考之自适应思维链
  • uv使用教程
  • FastMCP本地构建Server和Clinet交互
  • 用Python绘制SM2国密算法椭圆曲线:一场数学与视觉的盛宴
  • 时间戳 + 签名机制