数位动规,就是对于数位进行动规(日常一句废话···)

  刚好今天听数位dp,就总结一下最近写的题吧。郭神说要学懂数位dp,还是要搞懂它内部是怎么工作的。比如一个有大小的数,我们在这里剥夺它作为一个整数的所有标签,它现在只不过是几个位置,我们要在上面按照一定的限制放置数字,除了我给的限制之外,他们位与位之间没有任何影响。

  比如15651,38983,对于我的限制来说,这两个数并没有什么本质区别,他们都是有五个位置的,不含前导零,并且完全回文的数字串,除此之外,什么也不是。

  所以,我们就要想办法把某些限制的数筛出来,这就是数位dp。对于数位dp,我见过循环递推的写法,当然在我这边更流行的是记忆化搜索,今天讲的重点也是记忆化搜索;通过我对数位dp的理解,我整理了一个类似于记忆化搜索模板的东西,请大家先从下面看一两道题之后再来看这个模板:

  这个dfs函数包含几个参数,比如说当前决策的位(len)、题目中给的限制条件所需要的一两个参数、当前是否在数值范围的上界、有时还有前导零问题。具体题目还需具体分析。请看下图:

这只是一个比较笼统的模板,具体题目更要灵活应用!

下面分享几道水题:

一、HDU 3555 Bomb

这个题一句话就是说求1~n中含49的数字有多少,那我们直接在状态中记录决策时的最后两位,出现四十九的最后返回1,否则0;大家看这道题,前导零不前导零就没什么影响,因为49又不可能出现在前导零中,所以既不会出现多余的答案,也不会漏掉正确的答案!

请看代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstring>
 5 #include<algorithm>
 6 using namespace std;
 7 const int MAXN=21;char ch;
 8 long long f[MAXN][10][2],n,m,k,dig[MAXN],len1;
 9 long long dfs(int len,int la1,int la2,bool flag,bool ap){
10     if(len>len1) return (int)ap;
11     if(!flag&&~f[len][la2][(int)ap]) return f[len][la2][(int)ap];
12     long long tot=0;int end=flag?dig[len]:9;
13     for(int i=0;i<=end;i++)
14     tot+=dfs(len+1,la2,i,flag&&(i==end),((la2==4)&&(i==9))||ap);
15     if(!flag) f[len][la2][(int)ap]=tot;return tot;
16 }
17 int main(){
18     scanf("%lld",&k);
19     while(k--){
20         len1=0;ch=getchar();long long ans=0;
21         while(!isdigit(ch)) ch=getchar();
22         while(isdigit(ch)) dig[++len1]=ch-'0',ch=getchar();
23         memset(f,-1,sizeof(f));ans=dfs(1,0,0,1,0);
24         printf("%lld\n",ans);
25     }
26 }

数位dp

代码很短,也好写,尤其是,还很模板,作为入门题在适合不过啦!

二、HDU 2089 不要62

这个题乍一看根上一题一模一样!仔细一看也是一模一样,状态里还是记两位,只不过在判62的基础上判一下有没有4就好了!

请看代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cmath>
 5 #include<algorithm>
 6 #include<cstring>
 7 #include<string>
 8 #include<queue>
 9 using namespace std;
10 long long f[10][10],d[10];
11 long long n,m,k,l,r;
12 void take()
13 {
14     f[0][0]=1;
15     for(int i=1;i<=7;i++)
16     for(int j=0;j<=9;j++)
17     for(int k=0;k<=9;k++)
18     if(j!=4&&!(j==6&&k==2))
19     f[i][j]+=f[i-1][k];
20     return ;
21 }
22 int solve(int n)
23 {
24     int ans=0,len=0;
25     while(n){
26         d[++len]=n%10;
27         n /= 10;
28     }
29     d[len+1]=0;
30     for(int i=len;i>=1;--i){
31         for(int j=0;j<d[i];j++){
32             if(d[i+1]!=6||j!=2)
33                 ans+=f[i][j];
34         }
35         if(d[i]==4||(d[i+1]==6&&d[i]==2)) break;
36     }
37     return ans;
38 }
39 int main()
40 {
41     take();
42     while(~scanf("%d%d",&m,&n)&&m&&n){
43         printf("%d\n", solve(n+1)-solve(m));
44     }
45     return 0;
46 }

数位dp

这道题根上一道题我用的方法不同,这是用循环做的,有兴趣的同学可以试着用模板虐一下这题,也可以学学循环写法拓宽思路。

三、HDU 3709 Balance Number

这个题也很模板,平衡的数嘛,我们就枚举作为支点的那个数,然后一个参数记录题目限制,直接套用模板。但是这个题对于0来说就用一点影响了,000000这种数,每一位都是支点,都会被计算一次,最后要从答案中把多计算的减去!

请看代码:

 1 #include<iostream>
 2 #include<cmath>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<algorithm>
 6 #define ll long long
 7 using namespace std;
 8 const int MAXN=20;
 9 ll f[MAXN][MAXN][MAXN*100],l,r;int t,dig[MAXN];
10 ll dfs(int pos,int x,int s,bool flag){
11     if(pos<=0) return !s;if(s<0) return 0;
12     if(!flag&&~f[pos][x][s]) return f[pos][x][s];
13     int end=flag?dig[pos]:9;ll ans=0;
14     for(int i=0;i<=end;i++)
15     ans+=dfs(pos-1,x,s+i*(pos-x),flag&&(i==end));
16     if(!flag) f[pos][x][s]=ans;return ans;
17 }
18 ll solve(ll x){
19     if(x<0) return 0;int len=0;ll ans=0;
20     while(x) dig[++len]=x%10,x/=10;
21     for(int i=1;i<=len;i++) ans+=dfs(len,i,0,1);
22     return ans-len+1;
23 }
24 int main(){
25     memset(f,-1,sizeof(f));
26     scanf("%d",&t);while(t--){
27         scanf("%lld%lld",&l,&r);
28         printf("%lld\n",solve(r)-solve(l-1));
29     }return 0;
30 }

数位dp

也特别短,几乎就是模板!

四、luoguP3413 萌数

这个题需要稍微稍微动下脑筋,也特别简单,含长度至少为2的回文部分,我们可以简单地想成两种情况!分别是奇回文和偶回文,我们想,一个奇回文一定含有一个长度为3的回文部分(我称之为回文核),一个偶回文一定含有一个长度为2的回文核,那么这个题就解决了,只需要记录当前决策前两个数的状态,分情况判断。但是这个题有一个问题,前导零个数超过2的都会被记录,需要处理前导零的问题。

请看代码:

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<string.h>
 4 #include<algorithm>
 5 using namespace std;
 6 struct data{
 7     long long a,b;//a表示所有情况,b表示已有回文
 8 }d[1005][11][11];
 9 char s1[1005],s2[1005];
10 int nl,nr,len,i,dp[1005][11][11],digit[8000],mod=1000000007;//dp数组表示是否记录过,digit表示数位数字
11 inline data dfs(int len,int state,int ss,bool flag,bool ff){//len表示当前位置,state,ss分别表示两位数字,flag表示是否要枚举到9,ff表示当前是否是这个数的第一个数字,即我说的那个注意点
12     if(len==0){
13         data zs; zs.a=1; zs.b=0;
14         return zs;}//边界
15     if(dp[len][state][ss]&&flag==false&&ff==false)return d[len][state][ss]; data z; z.a=z.b=0;//如果已经访问过直接返回
16     int meiju=flag?digit[len]:9; //确定枚举范围
17     for(int i=0;i<=meiju;i++){
18         data zs=dfs(len-1,ff&i==0?10:i,ff&&i==0?10:state,flag&&(i==digit[len]),ff&&(i==0));
19         z.a+=zs.a; z.b+=zs.b; if(state==i||ss==i)z.b+=zs.a-zs.b; z.a%=mod; z.b%=mod;//是否产生回文
20     }
21     if(flag==false&&ff==false){dp[len][state][ss]=true;d[len][state][ss]=z;} //存储
22     return z;
23 }
24 int main(){
25     memset(dp,0,sizeof(dp));
26     scanf("%s%s",&s1,&s2);
27     nl=strlen(s1); nr=strlen(s2);len=nl;
28     for(i=0;i<nl;i++)digit[len-i]=s1[i]-'0'; int flag=false;
29     for(i=0;digit[i]==0;i++); digit[i]--; if(i==1&&digit[i]==1)nl--; for(i--;i;i--)digit[i]=9;//将l减1
30     long long zs=dfs(len,10,10,true,true).b;
31     len=nr;
32     for(int i=0;i<nr;i++)digit[len-i]=s2[i]-'0';
33     cout<<(dfs(len,10,10,true,true).b-zs+mod)%mod<<endl;
34 }

数位dp

我记得好像写过这题题解。额不对,我写的是Round Number!

五、当然是Round Number咯

甭解释了,写的非常清楚了!!

六、luogu 2657 Windy数

这个提也比较模板,可以说是只记录两个数就好,但我用的貌似是循环做法,可以看一下。

请看代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstdlib>
 5 #include<cstring>
 6 #include<string>
 7 #include<algorithm>
 8 #include<queue>
 9 using namespace std;
10 int f[15][15],n,m,l,k,r;
11 int a[15];
12 void init()
13 {
14     for(int i=0;i<=9;i++) f[1][i]=1;
15     for(int i=2;i<=10;i++){
16         for(int j=0;j<=9;j++){
17             for(int k=0;k<=9;k++){
18                 if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
19             }
20         }
21     }
22 }
23 int solve(int x)
24 {
25     memset(a,0,sizeof(a));
26     int len=0,ans=0;
27     while(x){
28         a[++len]=x%10;
29         x/=10;
30     }
31     for(int i=1;i<=len-1;i++){
32         for(int j=1;j<=9;j++){
33             ans+=f[i][j];
34         }
35     }
36     for(int i=1;i<a[len];i++){
37         ans+=f[len][i];
38     }
39     for(int i=len-1;i;i--){
40         for(int j=0;j<=a[i]-1;j++){
41             if(abs(j-a[i+1])>=2) ans+=f[i][j];
42         }
43         if(abs(a[i+1]-a[i])<2) break;
44     }
45     return ans;
46 }
47 int main()
48 {
49     init();
50     scanf("%d%d",&n,&m);
51     cout<<solve(m+1)-solve(n)<<endl;
52     return 0;
53 }

数位dp

处理边界的那一段长一些。

七、luogu 2602 数字计数

这个题就不是纯模板了,放在这里,权当开发智力!这个题需要试几组比较整的数来找找规律,同时还要注意比如0,1之类的细节,思虑周全再写会更好!

请看代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<algorithm>
 5 #include<cstdlib>
 6 #include<cstring>
 7 #include<string>
 8 #include<queue>
 9 using namespace std;
10 long long a,b;
11 long long ten[20],f[20],cnta[20],cntb[20];
12 void solve(long long x,long long *cnt)
13 {
14     long long num[20];
15     int len=0;
16     while(x){
17         num[++len]=x%10;
18         x/=10;
19     }
20     for(int i=len;i;i--){
21         for(int j=0;j<=9;j++){
22             cnt[j]+=f[i-1]*num[i];
23         }
24         for(int j=0;j<num[i];j++){
25             cnt[j]+=ten[i-1];
26         }
27         long long num2=0;
28         for(int j=i-1;j>=1;j--){
29             num2=num2*10+num[j];
30         }
31         cnt[num[i]]+=num2+1;
32         cnt[0]-=ten[i-1];//处理前导零问题;
33     }
34 }
35 int main()
36 {
37     scanf("%lld%lld",&a,&b);
38     ten[0]=1;//辅助数组,代表10的i次方
39     for(int i=1;i<=15;i++){
40         f[i]=f[i-1]*10+ten[i-1];
41         ten[i]=ten[i-1]*10;
42     }
43     solve(a-1,cnta);
44     solve(b,cntb);
45     for(int i=0;i<=9;i++) {
46         cout<<cntb[i]-cnta[i]<<" ";
47     }
48     puts("");
49     return 0;
50 }

数位dp

八、如果说前面都是水题,那这一道算是不那么水的题。luogu 3286 方伯伯的商场之旅

我们考虑代价是怎样计算的,从Balanced Number那里,我们可以得到一些启发,大概也是要找某个算起来比较平衡的数位来贡献代价!我们仔细观察,假如我们要把支点从当前位向右移动一位,代价会怎样变化?于是乎我们想到了用前缀和来计算支点的偏移,这样,我们通过两个dfs函数,首先,假定所有数的支点都在第一位(最左边一位),然后一位一位向右偏移,如果当前偏移能减小代价,那么就用答案减去这个值,否则就答案减去0;一直推到最后一位,这个答案就确定了!附一详细题解:errrrrrrrr

请看代码:

 1 //%%%%%%%尹兄
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<algorithm>
 7 #define ms(a,x) memset(a,x,sizeof(a))
 8 #define ll long long
 9 using namespace std;
10 const int MAXN=60;
11 ll f[MAXN][MAXN*50],l,r;
12 int k,n=0,dig[MAXN];
13 ll dfs1(int len,int s,bool flag){
14     if(len<=0) return s;
15     if(!flag&&~f[len][s]) return f[len][s];
16     int end=flag?dig[len]:k-1;ll ans=0;
17     for(int i=0;i<=end;i++)
18     ans+=dfs1(len-1,s+i*(len-1),flag&&i==end);
19     if(!flag) f[len][s]=ans;return ans;
20 }
21 ll dfs2(int len,int s,int m,bool flag){
22     if(s<0) return 0;if(len<=0) return s;
23     if(!flag&&~f[len][s]) return f[len][s];
24     int end=flag?dig[len]:k-1;ll ans=0;
25     for(int i=0;i<=end;i++)
26     ans+=dfs2(len-1,s+(len>=m?i:-i),m,flag&&i==end);
27     if(!flag) f[len][s]=ans;return ans;
28 }
29 ll solve(ll x){
30     n=0;while(x) dig[++n]=x%k,x/=k;
31     ms(f,-1);ll ans=dfs1(n,0,1);
32     for(int i=2;i<=n;i++)
33     ms(f,-1),ans-=dfs2(n,0,i,1);
34     return ans;
35 }
36 int main(){
37     scanf("%lld%lld%d",&l,&r,&k);
38     printf("%lld\n",solve(r)-solve(l-1));
39     return 0;
40 }

数位dp

可以看到,两个函数都是由模板改过来的,所以这题也没有那么难

通过上述这些题目我们可以看到,大部分的数位dp,都是通过模板进行一个变形,万变不离其宗,有的时候一些玄学优化会加大他的难度,所以数位dp说难也难,说不难也不难请各位勇敢的直面它。

转载于:https://www.cnblogs.com/Alan-Luo/articles/9514428.html

关于数位动规(入门到进阶,难度中档)相关推荐

  1. 动规日常训练题解 难度普及+

    9.6 动规训练  题解 ----Frosty_Jackal 定义Dpmax[i][j] 表示l~r之间最大的得分,由题意得拆环为链,将1~n的枚举范围扩大到1~2*n ,外层枚举区间长,内层枚举l, ...

  2. flask web开发是前端还是后端_Flask Web开发实战:入门、进阶与原理解析 PDF 全格式版...

    给大家带来的一篇关于Flask相关的电子书资源,介绍了关于Flask.Web.开发实战方面的内容,本书是由机械工业出版社出版,格式为PDF,资源大小12.2M,李辉编写,目前豆瓣.亚马逊.当当.京东等 ...

  3. 机器学习从入门到进阶✅

    1. 放弃海量资料!!! 没错,就是放弃海量资料!在我们想要入门机器学习的时候,往往会搜集很多资料,什么 xx学院机器学习内部资源.机器学习从入门到进阶百 G 资源.xx 人工智能教程,等等.很多时候 ...

  4. ICPC程序设计题解书籍系列之九:罗勇军《算法竞赛入门到进阶》

    罗书<算法竞赛入门到进阶>题目一览 第1章 算法竞赛概述 HDU1000 HDU1089-HDU1096 A+B for Input-Output Practice (I)-(VIII)( ...

  5. 移动 webApp 开发入门与进阶

    移动 webApp 开发入门与进阶 简介 一.移动 Web 开发入门 1.像素 2.视口(viewport) 3.box-sizing 属性 4.图标字体 5.flex 布局 (1)flex 的基本概 ...

  6. 干货!区块链入门、进阶、行业专家观点!1000篇好文帮你破解区块链密码!(中篇)...

    随着区块链概念理论的不断成熟以及强劲技术的不断深耕,区块链已经成为投资圈中备受关注的热点,从区块链1.0时代落地数字货币比特币.莱特币等,打开了区块链通向新弯道的高速路口,到区块链2.0时代开始通过智 ...

  7. 视频教程-Kali Linux渗透测试基础入门到进阶实战全程课-渗透测试

    Kali Linux渗透测试基础入门到进阶实战全程课 本人有多年的服务器高级运维与开发经验,擅长计算机与服务器攻防及网络攻防技术!对网络安全领域有持续的关注和研究! 林晓炜 ¥499.00 立即订阅 ...

  8. SQL Server AlwaysON从入门到进阶(6)——分析和部署AlwaysOn Availability Group

    本文属于SQL Server AlwaysON从入门到进阶系列文章 前言: 本节是整个系列的重点文章,到现在,读者应该已经对整个高可用架构有一定的了解,知道独立的SQL Server实例和基于群集的S ...

  9. 视频教程-Ajax从入门到进阶视频课程(通俗易懂)-JavaScript

    Ajax从入门到进阶视频课程(通俗易懂) 南京大学软件工程硕士,全栈开发工程师,全栈讲师. 曾就职于中软国际.擎天科技.华为等公司,擅长Java开发.Web前端.Python爬虫.PHP等领域技术.从 ...

最新文章

  1. vs2019下载和更新速度非常慢的解决方案
  2. maven——pom.xml
  3. java堆内与堆外数据交互_Java:汇总堆外数据
  4. xml 纯内容标签_Python小课堂XML 解析
  5. 在Unity 3D中,shader是何时编译的,在何时加载入显存中的?
  6. Flink : Flink JobManager报错 akka.pattern.AskTimeoutException: Ask timed out on
  7. Leetcode每日一题:36.valid-sudoku(有效的数独)
  8. pytorch创建datset
  9. 一款用来下载pdf word zip img各种文件的js插件
  10. 容智RPA可以在医疗哪些业务上降本增效
  11. 如何在UEFI模式下安装Linux,详解 UEFI 模式下安装 Linux
  12. springboot+vue+elementUI springboot地方废物回收机构管理系统-#毕业设计
  13. 看美文,记单词(5)
  14. 西安恒智小寨java_Java常用异常整理
  15. 如何对儿童虐待照片进行地理定位?
  16. Android 6.0指纹识别App开发demo
  17. 头歌-EduCoder:袋鼠过河问题
  18. 在UNIX \ Linux终端中的可视化磁盘空间和磁盘使用情况小工具 - Vizex
  19. 一加7T手机如何开启NFC、复制门禁卡等功能
  20. 购买商品复选框全选 单选

热门文章

  1. 固态存储作缓存 提升性能有绝招
  2. Windows与VMware下的Linux文件共享方式总结
  3. 爱情,没有对不起;只有不珍惜……[
  4. 单片机c语言程序设计实训报告,(整理)单片机C语言程序设计实训100例.doc
  5. 【工具类】页面静态化 --- Freemarker的使用
  6. postman发送json请求,使用案例
  7. 选择company回显appname
  8. jQuery简单的Ajax调用示例
  9. DedeCMS(织梦)安全设置经验分享
  10. Win2000 Win2003安装卡巴斯基6.0