\(update:2019-9-6\)

博客里某些东西没有解释清楚,完善了对应的解释


在开始之前,我们先来看一道题——题目链接

题目要求,相邻两位的差大于等于2,那么我们先来构造一个试一试。

比如说\(15246\)这个数,我们先取第一位为\(1\),然后第二位是\(5\),\(5-1=4>2\)所以符合条件,第三位是\(2\),\(5-2=3>2\)符合条件,第四位是\(4\),\(4-2=2\)符合条件,第五位是\(6\),\(6-4=4\)符合条件,所以这个数使符合条件的。

那么问题来了,如果我们一个数一个数的构造,复杂度显然是有问题的,我们就需要对其进行优化。来看\(15246\)和\(96246\)这两个数,他们都是符合规则的,而且它们后三位是相同的,那么我们很容易可以联想到,只要倒数第四位与\(2\)的差符合规则,那么只要后三位是\(246\)就一定是符合规则的,也就是说我们根本不需要去重复判断。

有没有想到什么熟悉的东西?没错,记忆化!从前往后构造,当后几位已经被处理过,我们就可以直接使用,而不是重新判断,大大节省了时间。那么我们就可以用\(f[pos][pre]\)来记录当前位为第\(pos\)位,上一位为数值为\(pre\),且不含前导零,没有卡在最大值上时,能够对答案产生的贡献。

那么判断也就很简单了,只需要枚举下一位,看和当前位的差是否满足规则即可。题目又要求不含前导零,也就是除了\(0\)本身以外的任何数不准用\(0\)开头,那么从第一位开始,我们记录有前导\(0\)当有一位不为\(0\)之后,把状态记录为不含前导零,前导零后的第一个不为零位无任何限制。

那么我们来看一下程序吧

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#include<cmath>
#define ll long long
#define gc() getchar()
#define maxn 15
using namespace std; inline ll read(){
ll a=0;int f=0;char p=gc();
while(!isdigit(p)){f|=p=='-';p=gc();}
while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
return f?-a:a;
}
void write(ll a){
if(a>9)write(a/10);
putchar(a%10+'0');
} int x,y,d[maxn],l;
ll f[maxn][maxn];
ll dfs(int pos,int pre,bool limit,bool lead){ //pos表示从后往前数第pos位,pre记录前一位我们选择的数值,limit记录当前是不是卡着最大值,如果卡着最大值就不能从[0,9]中任意选数,而是在[0,区间右端点当前位的数值]之间选数,lead就是记录前导零的问题了
if(!pos)return 1; //如果所有位都已经构造完了,说明这是一个合法数值,贡献加一
if(!limit&&!lead&&~f[pos][pre])return f[pos][pre]; //如果已经处理过特殊要求均相同的情况,直接返回答案,避免重复计算
int up=limit?d[pos]:9;ll ans=0; //up就是当前位选数的右端点
for(int i=0;i<=up;++i){ //枚举构造
if(abs(i-pre)<2&&!lead)continue; //如果不合法则跳过
ans+=dfs(pos-1,i,limit&&i==d[pos],lead&&!i);
}
if(!limit&&!lead)f[pos][pre]=ans; //这里有多种写法,其实就是要求你把各种特殊状态都记录下来
return ans;
} ll solve(int k){
l=0;
while(k){ //这里是为了记录一下当前范围最大是几位
d[++l]=k%10;
k/=10;
}
return dfs(l,0,1,1);
} int main(){memset(f,-1,sizeof f);
x=read();y=read();
write(solve(y)-solve(x-1)); //答案要求是[x,y]之间的windy数,所以减去[0,x)的windy数即可
return 0;
}

下面我们再来看两道题,一道是ZJOI2010数字计数,另一道是CQOI2016的手机号码

对于这两道题,还是跟刚才一样——先构造。


数字计数这道题就是要求统计每个数出现的次数,那么我们只需要分开统计,每次只统计一个数字,重复十次即可,需要记录的特殊情况就是前导零还有边界情况

下面是代码(就不再附详细解释了)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc() getchar()
#define maxn 15
using namespace std; inline ll read(){
ll a=0;int f=0;char p=gc();
while(!isdigit(p)){f|=p=='-';p=gc();}
while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
return f?-a:a;
}
void write(ll a){
if(a>9)write(a/10);
putchar(a%10+'0');
} int l,d[maxn];
ll x,y,f[maxn][maxn];
ll dfs(int pos,int s,int x,bool limit,bool lead){
if(!pos)return s;
if(!limit&&!lead&&~f[pos][s])return f[pos][s];
int up=limit?d[pos]:9;ll ans=0;
for(int i=0;i<=up;++i)
ans+=dfs(pos-1,s+(i==0?(!lead&&x==0):i==x),x,limit&&i==d[pos],lead&&!i);
if(!limit&&!lead)f[pos][s]=ans;
return ans;
} ll solve(ll k,int a){
l=0;
while(k){
d[++l]=k%10;
k/=10;
}
return dfs(l,0,a,1,1);
} int main(){
x=read();y=read();
for(int i=0;i<=9;++i){
memset(f,-1,sizeof f);
printf("%lld ",solve(y,i)-solve(x-1,i));
}
return 0;
}

那么对于手机号码这道题,需要记录的特殊状态就比较多了,分别是前导零,边界情况,前两位的数值以及是否出现8和是否出现4

那么代码还是大同小异,只是加了几个判断而已

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc getchar
#define maxn 15
using namespace std; inline ll read(){
ll a=0;int f=0;char p=gc();
while(!isdigit(p)){f|=p=='-';p=gc();}
while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
return f?-a:a;
}ll l,r;int d[maxn]; ll f[maxn][maxn][maxn][5][5][5][5];
ll dfs(int pos,int pre,int pre2,int limit,int c,int ba,int si){
if(ba&si)return 0;
if(!pos)return c;
if(~f[pos][pre][pre2][limit][c][ba][si])
return f[pos][pre][pre2][limit][c][ba][si];
int up=limit?d[pos]:9,down=pos==11;ll sum=0;
for(int i=down;i<=up;++i)
sum+=dfs(pos-1,i,pre,limit&(i==up),c|(i==pre&&i==pre2),ba|(i==8),si|(i==4));
return f[pos][pre][pre2][limit][c][ba][si]=sum;
}
inline ll solve(ll x){memset(f,-1,sizeof f);
memset(d,0,sizeof d);
if(x<1e10)return 0;
int len=0;
while(x){
d[++len]=x%10;
x/=10;
}
return dfs(len,11,11,1,0,0,0);
} int main(){
l=read();r=read();
printf("%lld\n",solve(r)-solve(l-1));
return 0;
}

题目推荐

[HAOI2010]计数

[AHOI2009]同类分布

那么数位DP大概就是这个样子,很简单,也很明显

如果这篇博客对你的学习有些许帮助,不妨点个推荐吧

数位DP入门详解+题目推荐的更多相关文章

  1. 状压DP入门详解+题目推荐

    在动态规划的题型中,一般叫什么DP就是怎么DP,状压DP也不例外 所谓状态压缩,一般是通过用01串表示状态,充分利用二进制数的特性,简化计算难度.举个例子,在棋盘上摆放棋子的题目中,我们可以用1表示当 ...

  2. 树形DP入门详解+题目推荐

    树形DP.这是个什么东西?为什么叫这个名字?跟其他DP有什么区别? 相信很多初学者在刚刚接触一种新思想的时候都会有这种问题. 没错,树形DP准确的说是一种DP的思想,将DP建立在树状结构的基础上. 既 ...

  3. HDU 1693 插头dp入门详解

    放题目链接   https://vjudge.net/problem/22021/origin 给出一个n*m的01矩阵,1可走0不可通过,要求走过的路可以形成一个环且可以有多个环出现,问有多少不同的 ...

  4. 数位DP模板详解

    // pos = 当前处理的位置(一般从高位到低位) // pre = 上一个位的数字(更高的那一位) // status = 要达到的状态,如果为1则可以认为找到了答案,到时候用来返回, // 给计 ...

  5. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  6. Redis快速入门详解

    Redis入门详解 Redis简介 Redis安装 Redis配置 Redis数据类型 Redis功能 持久化 主从复制 事务支持 发布订阅 管道 虚拟内存 Redis性能 Redis部署 Redis ...

  7. xbz分组题B 吉利数字 数位dp入门

    B吉利数字时限:1s [题目描述]算卦大湿biboyouyun最近得出一个神奇的结论,如果一个数字,它的各个数位相加能够被10整除,则称它为吉利数.现在叫你计算某个区间内有多少个吉利数字. [输入]第 ...

  8. hdu3555 Bomb 数位DP入门

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3555 简单的数位DP入门题目 思路和hdu2089基本一样 直接贴代码了,代码里有详细的注释 代码: ...

  9. [置顶] xamarin android toolbar(踩坑完全入门详解)

    网上关于toolbar的教程有很多,很多新手,在使用toolbar的时候踩坑实在太多了,不好好总结一下,实在浪费.如果你想学习toolbar,你肯定会去去搜索androd toolbar,既然你能看到 ...

随机推荐

  1. 个人app如何收集用户日志

    版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/147 个人app如何收集用户日志,相信不少人对这个话题感兴 ...

  2. WASM 成为 HTML、CSS 与 JS 之后的第 4 门 Web 语言

    大家都知道,万维网联盟 W3C 认证的 Web 语言有 HTML.CSS 与 JavaScript,而近日联盟正式宣布 WebAssembly 核心规范(WebAssembly Core Specif ...

  3. Network出现两次相同请求?

    出现的状况 Network中出现了两个相同的请求(如图),两个发起了同样的请求,花的时间却不同,一个55ms,一个花了294ms.   两个相同的请求 什么情况啊?研究了一番,我发现有一个地方是不同的 ...

  4. 关于中医的一段对话 [ZZ] -- 思维训练故事

    转载自新浪博客 网址: http://blog.sina.cn/dpool/blog/s/blog_9880df4201015khq.html?type=-1 关于中医的一段对话 2013-01-26 ...

  5. C#添加错误日志信息

    错误日志是软件用来记录运行时出错信息的文本文件.编程人员和维护人员等可以利用错误日志对系统进行调试和维护. 系统日志 系统日志包含了由Windows系统组件记录的事件.例如,在启动期间装入驱动程序或其 ...

  6. 如何获得大学教材的PDF版本?

    最近急需一本算法书的配套答案,这本配套单独出售,好像在市面上还买不到,在淘宝上搜索也只是上一个版本,并没有最新版本,让我很无奈.加上平时肯定会有这么一种情况,想看一些书,但买回来也看不了几次,加上计算 ...

  7. 我来告诉你:VS2019开发ASP.NET Core 3.0 Web项目,修改视图后,刷新浏览器看不到修改后的效果怎么处理

    VisualStudio2019下一个2.2另一个3.0页面修改如下,但是3.0刷新没有任何变化,难道VS以后不能做前端开发了?大家可能没有看官方文档 根据文章所说你需要: 1.安装 Microsof ...

  8. [WPF 自定义控件]开始一个自定义控件库项目

    1. 目标 我实现了一个自定义控件库,并且打算用这个控件库作例子写一些博客.这个控件库主要目标是用于教学,希望通过这些博客初学者可以学会为自己或公司创建自定义控件,并且对WPF有更深入的了解. 控件库 ...

  9. Asp.Net Core 内置IOC容器的理解

    Asp.Net Core 内置IOC容器的理解 01.使用IOC容器的好处 对接口和实现类由原来的零散式管理,到现在的集中式管理. 对类和接口之间的关系,有多种注入模式(构造函数注入.属性注入等). ...

  10. JavaScript-三种弹窗方式

    0918自我总结 JavaScript-三种弹窗方式 一.alert 带内容的弹框 用法: <script> alert('弹窗显示的内容') //会弹出框没有点确定不会执行下面的代码会发 ...