【学时·IV】 数位DP


■基本策略■

说白了就是超时和不超时的区别 ;)

有一些特别的题与数位有关,但是用一般的枚举算法会超时。这时候就有人提出了——我们可以用动态规划!通过数字前一位和后一位之间的关系,逐渐推导出所有数位上的值作为初始化(也有些不是),实现大部分计数问题的高效解决。

主要题型大概就是求 Min~Max 之间满足条件 E() 的数的个数,这里使用了前缀和的思想,即 F[i~j]=F[0~ j]-F[0~(i-1)]。一般是将 F[] 初始化,但是针对某些特别题型,比如 E() 针对不同的数据不同,这时候需要对每一个数据单独求解。


■一般的计数问题■

在十进制里做DP ♪(´▽`)

◆入门练手◆ 不要62

  • 【HDU - 2089】
  • 【解析】

这是一道基本上称得上“版题”的基础题。先给出m,n,也就是 Min,Max 。这里的 E(n) 则是 n不含4或62。
先给出一个最基本的结论:

若A < B,则A、B的十进制表示 {an...a2a1a0}、{bn...b2b1b0}必存在 ar=br(k < r < n)且 ak < bk

接下来是动态规划的初始化——令F[i][j]表示第i位是j的数的方案总数。当满足第i位(j)不为4,第i-1位(k)不为2或者i-1位为2但是第i位不为6:

若当前枚举到第i位,第i位数字为j,第i-1位数字为k

当且仅当j≠4且k≠2或者k=2但j≠6时,有dp[i][j]=sum(dp[i-1][k])

通过三层从小到大的循环完成i,j,k的枚举。

这是我们继续建立计算的基础——先用dig[]储存m、n的十进制表示,然后从高位开始枚举0~dig[i]-1,保证枚举出的数小于给出的数,且。设当前枚举位为j,如果满足 E() ,就在答案中加上 F[i][j]。

  • 【源代码】
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int F[15][15];
void GetDig(int num,int dig[])
{do{dig[++dig[0]]=num%10;num/=10;}while(num);
}
void GetF()
{F[0][0]=1;for(int i=1;i<=7;i++)for(int j=0;j<10;j++)for(int k=0;k<10;k++)if((j!=4) && !(j==6 && k==2))F[i][j]+=F[i-1][k];
}
int GetAns(int num,int dig[])
{int ret=0;for(int i=dig[0];i>0;i--){for(int j=0;j<dig[i];j++){if((j!=4) && !(j==2 && dig[i+1]==6))ret+=F[i][j];}if((dig[i]==4) || (dig[i]==2 && dig[i+1]==6))break;}return ret;
}
int main()
{GetF();int m,n;while(~scanf("%d%d",&n,&m) && n && m){int dign[15]={},digm[15]={};GetDig(n,dign);GetDig(m+1,digm);int ansn=GetAns(n,dign),ansm=GetAns(m+1,digm);printf("%d\n",ansm-ansn);}return 0;
}

◆有点意思◆ B-number

这道题做不做的出来,你心里难道就没有一点B数吗?

  • HDU - 3652
  • 【解析】

每个人心中都有一个B数(向yhn大佬致敬)。这道题其实是上一个题的升级版。

万事开头难,先看看状态定义:

dp[i][j][k] (满足0 ≤ i < len,0 ≤ j < 13,0 ≤ k < 3)

表示第i位时当前数模13余j数中,满足条件E(k)的数的个数;

E(k):k=0时,不存在13;k=1时,不存在13但是存在3;k=2时,不存在1或3

这里的第三维其实也可以用k表示当前数位的数字,但是显然可以更加优化!有时候只需要储存一些会对答案产生影响的条件作为一维。虽然减少了空间消耗,但是作为代价,思维的复杂程度和转移方程的错误率也相应增加,初学者还是不要尝试这种方法。(`・ω・´)

由于这道题有多组数据(o(゚Д゚)っ!),而且每一组数据相应的dp数组值不一样。这就意味着要针对每一组数据单独进行一次DP求解——这是数位DP中较特殊的题型。

与一般数位DP相同,我们仍需考虑n的数位。但是因为题目默认是1~n,就没有必要进行前缀和的操作了,我们可以同时考虑数位和DP转移,但是DP参数就会更复杂:
ll DP(int pos,int mod,int lst,bool lim,bool flg) //你没有看错,是long long /* pos: 现在枚举到的位置 mod: 当前数模13的余数 lst: 上一个数位的数字 lim: 是否达到数位限制* flg: 当前是否已经存在13 */
*:

什么叫数位限制?

我们令n的10进制表示为dig[0~len-1]、枚举的数字m的10进制表示为fdig[0~len-1],若我们现在从最高位枚举到第k位,满足dig[r]==fdig[r](k < r < len),则称第k数位的枚举受数位限制,此时的fdig[k]只能取 0~dig[k];如果不受数位限制,则fdig[k]可取0~9!

总而言之,有了数位限制才能控制枚举出的数是小于等于n的数!

特别声明:数的最高位总受数位限制

于是我们发现,dp[][][] 只能储存当前位不受数位限制的答案,所以当前位受限制时不能将答案储存在dp里,同样,在记忆化搜索的记忆性返回的时候要保证现在不受数位限制。

接下来就是转移方程了:

令tot=sum(dp[pos-1][(mod*10+1)%mod][]),第三维由记忆化的返回值决定;

当flg==true时,dp[pos][mod][0]=tot;
当flag==false且lst==1时,dp[pos][mod][1]=tot;
当flag==false且lst!=1时,dp[pos][mod][2]=tot;

我知道看上面的这么多内容很难理解,所以下面放代码啦。

  • 【源代码】
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
#define MOD 13
ll dp[40][13][3];
//[i][j][k]:k=2 - no 1 or 3; k=1 - no 13 but 3; k=0 no 13
int num[15];
ll DP(int pos,int mod,int lst,bool lim,bool flg)
{if(pos<0){if(flg && !mod) return 1ll;else return 0ll;}if(dp[pos][mod][0]!=-1 && !lim && flg) return dp[pos][mod][0];if(dp[pos][mod][1]!=-1 && !lim && lst==1 && !flg) return dp[pos][mod][1];if(dp[pos][mod][2]!=-1 && !lim && lst!=1 && !flg) return dp[pos][mod][2];int limit=lim? num[pos]:9;ll tot=0;for(int i=0;i<=limit;i++){bool lflg=(flg||(lst==1 && i==3)),llim=(lim && (i==limit));tot+=DP(pos-1,(mod*10+i)%MOD,i,llim,lflg);}if(!lim){if(flg) dp[pos][mod][0]=tot;if(!flg && lst==1) dp[pos][mod][1]=tot;if(!flg && lst!=1) dp[pos][mod][2]=tot;}return tot;
}
ll GetAns(int n)
{int len=0;memset(num,0,sizeof num);while(n)num[len++]=n%10,n/=10;return DP(len-1,0,0,true,false);
}
int main()
{memset(dp,-1,sizeof dp);int n;while(~scanf("%d",&n))printf("%lld\n",GetAns(n));return 0;
}

■异进制的世界■

◆预备烧脑◆ Amount of Degrees

  • URAL - 1057
  • 【解析】
    其实就是求区间[X,Y]中有多少个数能表示成一个B进制的01数串,并且1的个数恰好为K(比如 B=3,K=2 时 101(3)=3^2+3^0=10(10))。
    求区间中的个数仍然是使用前缀和的思想。
    但是由于B进制并不方便思考,所以将B进制强制转换为2进制(重点:将除10进制外的所有进制按照2进制强制处理),这也是为什么题目给的Y是以2的幂为上限(Y≤2^31-1)。
    定义 F[i][j] 为满足条件的长度为i,包含j个1的二进制数。容易得到转移方程:

F[i][j]=F[i-1][j]+F[i-1][j-1]

之后就是处理答案。也就是GetAns(),其实和dig[]的思路一样,只是因为用2进制储存,还要比十进制更简单——只需要判断当前位是0还是1就可以了。

  • 【源代码】
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int F[40][40];
void init()
{F[0][0]=1;for(int i=1;i<40;i++){F[i][0]=F[i-1][0];for(int j=1;j<=i;j++)F[i][j]=F[i-1][j]+F[i-1][j-1];}
}
int GetAns(int num,int mul,int mod)
{int sit[40]={},siz=1,ret=0;while(num) sit[siz++]=num%mod,num/=mod;for(int i=siz-1;i>0 && mul>=0;i--){if(sit[i]>1) {ret+=F[i-1][mul-1]+F[i-1][mul];break;}if(sit[i]==1) ret+=F[i-1][mul],mul--;}return ret;
}
int main()
{init();int x,y,k,b;scanf("%d%d%d%d",&x,&y,&k,&b);int ans1=GetAns(y+1,k,b),ans2=GetAns(x,k,b);printf("%d\n",ans1-ans2);return 0;
}

The End

Thanks for reading!

-Lucky_Glass

转载于:https://www.cnblogs.com/LuckyGlass-blog/p/9121151.html

【学时总结】 ◆学时·IV◆ 数位DP相关推荐

  1. 【校内模拟】A(容斥原理)(数位DP)(范德蒙德恒等式)(高精度)

    简要题意: 请你对满足下列条件的正整数序列 A1,A2⋯AnA_1,A_2\cdots A_nA1​,A2​⋯An​ 进行计数. ∀1≤i≤n,Li≤Ai≤Ri\forall 1\leq i\leq ...

  2. 【bzoj 1833】【codevs 1359】 [ZJOI2010]count 数字计数(数位dp)

    1833: [ZJOI2010]count 数字计数 Time Limit: 3 Sec  Memory Limit: 64 MB Submit: 2774  Solved: 1230 [Submit ...

  3. 不要62 ---数位DP

    题意:求m到n中不含62和4的数的个数. 题目链接 思路:数位dp模板求满足的数字或不满足的数字,刚学,就求不满足的数. #include<stdio.h> #include<str ...

  4. bzoj 3598 [ Scoi 2014 ] 方伯伯的商场之旅 ——数位DP

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3598 数位DP...东看西看:http://www.cnblogs.com/Artanis/ ...

  5. cojs 简单的数位DP 题解报告

    首先这道题真的是个数位DP 我们考虑所有的限制: 首先第六个限制和第二个限制是重复的,保留第二个限制即可 第五个限制在转移中可以判断,不用放在状态里 对于第一个限制,我们可以增加一维表示余数即可 对于 ...

  6. 数位DP 不断学习中。。。。

    1, HDU 2089  不要62 :http://acm.hdu.edu.cn/showproblem.php?pid=2089 题意:不能出现4,或者相邻的62, dp[i][0],表示不存在不吉 ...

  7. [数位dp] spoj 10738 Ra-One Numbers

    题意:给定x.y.为[x,y]之间有多少个数的偶数位和减去奇数位和等于一. 个位是第一位. 样例: 10=1-0=1 所以10是这种数 思路:数位dp[i][sum][ok] i位和为sum 是否含有 ...

  8. 数位dp(求1-n中数字1出现的个数)

    题意:求1-n的n个数字中1出现的个数. 解法:数位dp,dp[pre][now][equa] 记录着第pre位为now,equa表示前边是否有降数字(即后边可不能够任意取,true为没降,true为 ...

  9. 数位dp ---- 2020 icpc 上海 Sum of Log(枚举高位的二进制数位dp)

    题目链接 题目大意 : 解题思路: 这里有个很核心的地方就是log2(i+j)\text{log2(i+j)}log2(i+j)本质上就是看看i+j\text{i+j}i+j的二进制高位在哪里? 那么 ...

  10. 数位dp ---- 暴力 + 二进制的数位dp 2020济南 L Bit Sequence

    题目链接 题目大意 f(x)=x的二进制中1的个数f(x)=x的二进制中1的个数f(x)=x的二进制中1的个数 给你一个数组[a1,a2,a3...,am]m∈[1,100][a_1,a_2,a_3. ...

最新文章

  1. node.js学习5--------------------- 返回html内容给浏览器
  2. golang实践LSM相关内容
  3. POJ 2516 基础费用流
  4. java 中sun.net.ftp_开发FTP不要使用sun.net.ftp.ftpClient
  5. WEB安全基础-SQL注入演示
  6. iOS与H5交互(WKWbebView)
  7. 呕心码出的代码无人看,做个优秀的技术人好难
  8. 如何将一个JavaScript数组打乱顺序?
  9. 逻辑电路 -异或门Xor Gate
  10. java 算法之快速排序
  11. MATLAB 50行代码绘制超好看的旋转九边形
  12. 字节跳动员工基本年薪曝光:最高 254 万元
  13. 期货软件开发-期货软件搭建平台
  14. 德州大学达拉斯分校计算机专业博士,德克萨斯大学达拉斯分校计算机科学理科硕士入学条件及实习就业...
  15. Oleg Shilo:基于CS-Script的Notepad++的插件_.Net技术
  16. TX2超详细,超实用刷机教程(亲测有效,所有步骤都是博主亲自实践过)
  17. 牛客网华为机试【汽水瓶】
  18. ffmpeg简易使用应用分享(m3u8下载与视频合并等)
  19. Toad for Oracle 使用说明
  20. oracle 字符串去重

热门文章

  1. PHP常用正则类(全)
  2. ArcGIS Desktop10.2与CityEngine2012兼容问题
  3. AS 3.0 socket 通信,比较基础比较全【转载】
  4. 对账不平问题专题讲解内容
  5. 把业务逻辑变成数据结构和SQL语句的例子。自然架构改成自然框架
  6. 2017.3.18 PPT汇报--总结
  7. MyBatis和Hibernate的优缺点对比。
  8. achartengine画出动态折线图
  9. 解决滑动UITableView自动显示delete按钮
  10. Ubuntu18.04 evo安装