总结数位DP算法_dp算法总结
总结数位DP算法由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“dp算法总结”。
数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数。比如,[1,10000] 中统计不含有4的数。
所谓数位dp,字面意思就是在数位上进行dp咯。就是对数字每一位每一位递推
此类题目最基本的暴力方法:
1.for(int i=le;i
2.if(Check(i))ans++;
而数位DP就是从最低(高)位起,一位一位的放数字,然后记忆化一下,累加一下
有两种方法,一是递推,二是记忆化搜索
一,记忆化搜索:
思路来自: 数位dp总结之从入门到模板 假设题目要求是不含有62的数
状态定义:d[pos][pre] 表示当前枚举到pos位置,且pos+1位的数字是pre,此时满足题意的数字的个数(也即是pre==6时,pos该位置不能放2)还要个数组a[i]保存第i位的数字,如213,a[0]=3,注意是从右往左数
有个问题是枚举第pos位数时,此位置放数字的范围要判断一下,比如题目给出在[1,894] 枚举的时候要判断是否在894以内
比如,213,第一位放了2,那么第二位就只能放0~1,所以模板中用了个limit判断pos前的几位数字是否与n一样,true的话只能枚举0~a[pos],false就是0~9,不然比题目要求的213大了
还有个问题是前导0的问题,假如枚举5位数,你放的时候前2位都是00,那数字不变成3位了嘛,所以需要个lead保存前几位是否都是0,当然这是看题意的,有时候题目不要求,可以直接省去
好了,看模板:
1.typedef long long ll;2.int a[20];
3.ll dp[20][state];//不同题目状态不同
4.ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零
5.{
6.//递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了
7.if(pos==-1)return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */ 8.//第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
9.if(!limit &&!lead && dp[pos][state]!=-1)return dp[pos][state];10./*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
11.int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了
12.ll ans=0;13.//开始计数
14.for(int i=0;i
15.{
16.if()...17.else if()...18.ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos])//最后两个变量传参都是这样写的
19./*这里还算比较灵活,不过做几个题就觉得这里也是套路了
20.大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
21.去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目
22.要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类,23.前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/
24.}
25.//计算完,记录状态
26.if(!limit &&!lead)dp[pos][state]=ans;
27./*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
28.return ans;29.}
30.ll solve(ll x)31.{
32.int pos=0;
33.while(x)//把数位都分解出来
34.{
35.a[pos++]=x%10;//个人老是喜欢编号为[0,pos),看不惯的就按自己习惯来,反正注意数位边界就行
36.x/=10;37.}
38.return dfs(pos-1/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
39.}
40.int main()41.{
42.ll le,ri;
43.while(~scanf(“%lld%lld”,&le,&ri))44.{
45.//初始化dp数组为-1,这里还有更加优美的优化,后面讲 46.printf(“%lldn”,solve(ri)-solve(le-1));47.} 48.}
注意:
那个if(!limit &&!lead &&dp[pos][state]!=-1)return dp[pos][state];limit 的数字必须要枚举,不能直接返回,每次都要算
虽然这会导致重复,但这可以解决状态冲突,而且重复计算的数字也很少 举例如下:
题目:不能出现连续的11(11、112、211都是不合法的)那么我们开始枚举:
要枚举3位数,已经枚举了两位01_,要枚举最后一位,此时状态为d[0][1] 即:在枚举个位,且前一位为1,那么显然得出d[0][1]=9 开始新的一轮枚举,枚举到11_,此时状态也是d[0][1] 因为已经有9这个值了,所以返回了,但很明显答案是0,是错的 当然可以多开一维防止状态冲突
可以看看数位DP模板题: HDU 2089 不要62 数位DP.二,递推方法
思路来自:初探数位dp
状态定义:d[i][j] 有i位数字,且第一位为j,在 0~j-1 + 000....999的符合题意的个数,如 d[4][3] 就是在 3000~3999 的符合题意的个数
还要个数组a[i]保存第i位的数字,如213,a[1]=3,注意是从右往左数(下面是从1开始数起了)
这样状态定义的能更加方便,可以预处理,因为当一个数字的第一位比题目要求的第一位小后,后面的几位能000..~999..如4269,如果第一位枚举 3 _ _ _,那么后三位可以任取
模板如下:
1.for(int i=1;i
2.{
3.for(int j=0;j
4.{
5.for(int k=0;k
6.{
7.if(j!=4&&!(j==6&&k==2))//符合题意的条件
8.dp[i][j] += dp[i-1][k];9.} 10.} 11.}
以HDU 2089,解释怎么算出答案(不含4,62的数字)
1.#include
2.#include 3.#include
4.#include
5.using namespace std;6.int d[10][10],digit[10];
7.//d[i][j] 表示有i位数字,且第一位是j的数字的 满足题意的数量
8.void init()9.{
10.d[0][0]=1;
11.for(int i=1;i
for(int j=0;j
for(int k=0;k
if(j!=4&&!(j==6&&k==2))15.d[i][j]+=d[i-1][k];16.}
17.int solve(int x)// [0,x)
18.{
19.int len=0;20.while(x){
21.digit[++len]=x%10;22.x/=10;23.}
24.digit[len+1]=0;25.int ans=0;
26.for(int i=len;i>=1;i--){
27.for(int j=0;j
28.if(j!=4&&!(j==2&&digit[i+1]==6))29.ans+=d[i][j];30.31.if(digit[i]==4||(digit[i+1]==6&&digit[i]==2))32.break;33.}
34.return ans;35.}
36.int main(int argc, char const *argv[])37.{
38.int n,m;39.init();
40.while(cin>>n>>m,n+m)41.cout
42.return 0;43.}
假设一个数3229 得出
0000~0999 的个数 1000~1999 的个数 2000~2999 的个数 000~099 的个数 100~199 的个数 00~99 的个数 10~19 的个数 0~8的个数 累加就是答案了
所以该区间是[0,n)是取不到的n的,注意计算的时候要加一个1
下面是一些题目:
HDU 2089 不要62和4 HDU 3555 含49的数
HDU 3652 含13且可以被13整除
codeforces 55d A 一个数字可以被它所有非零数整除的个数 POJ 3252 Round Numbers HDU 4734 F(x)HDU 3709 Balanced Number HYSBZ 1799 self 同类分布
URAL 1057 Amount of Degrees * HDU 4507 吉哥系列故事——恨7不成妻 *
总结:
可能要用到的数位DP的题目类型:
1~10^18,求某区间(很大),有特定要求的数字的个数 如求mod,求和,可以整除各位数,不出现某些数...框架:
int DFS(intpos,......)//DFS一位一位放数字,求出答案,函数的参数保存题目要求的状态
int solve(int n)//把n一位一位拆分,求出[1,n] 的符合要求的值
难点:定义好状态!
1.dp状态要找好,不要出现状态重叠现象,注意前导0有没有影响
2.题目有求和sum,可能会很大,但可以转化为保存sum对一个数求mod的值 3.有时候dp状态定义不好可能要求每次DFS都要memset一下,换换思路想想通用的状态定义,如sum从加法改为减法
算法分块总结为备战2005年11月4日成都一战,特将已经做过的题目按算法分块做一个全面详细的总结,主要突出算法思路,尽量选取有代表性的题目,尽量做到算法的全面性,不漏任何ACM可......
算法分析与设计总结报告71110415 钱玉明在计算机软件专业中,算法分析与设计是一门非常重要的课程,很多人为它如痴如醉。很多问题的解决,程序的编写都要依赖它,在软件还是面向过......
认识数位教学目标:知识与技能:。通过计数器使学生认识个位,十位、百位,了解个位、十位上数表示含义,会正确读、写100以内的数。过程与方法:。通过动手操作、观察,直观体验数的意义,......
算法总结1.穷举法穷举法,又称暴力算法,即列举问题解空间所有可能情况,并逐个测试,从而找出符合问题条件的解。这份通常是一种费时算法,人工手动求解困难,但计算机的出现使得穷举法......
abs(x):y 取x的绝对值,x与 y可为整型或实型。* frac(x):y 取x的小数部分,x 与 y均为实型。* int(x):y 取x的整数部分,x 与 y均为实型,常写成 trunc(int(x)). * random(x):y......
