数位DP
解决的是求一段区间中满足一定条件的数的数量,形式固定
数位DP模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int num[100],dp[100][...]; int dfs(int pos,int ...,bool limit) { if(pos==0) return ; if(!limit && dp[pos][...]!=-1) return dp[pos][...]; int up=limit?num[pos]:9; int sum=0; for(int i=0; i<=up; i++) { if(...) sum+=dfs(pos-1,...,limit && (i==up)); } if(!limit) dp[pos][...]=sum; return sum; }
int work(int x) { int len=0; while(x) { num[++len]=x%10; x=x/10; } return dfs(len,...,1); }
|
以一道例题讲解算法
求[L,R]内所有没出现4、62的数字数量,比如662、458这些都不满足条件。
首先转化为算1~R满足条件数字的问题,然后用fun(R)-fun(L-1)就是答案,数位DP就是对数位进行记忆化搜索,保存可以利用的信息,从而进行剪枝优化时间。核心在于dp[][]数组去保存信息,第一维表示当前的位数,第二维表示前一位的状态,记录的是这一维没有限制时符合条件的数量,也就是当前这一位是0~9时的数量,因为这个状态重复的最多且计算量大
这就是这道题目的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| int dfs(int pos,bool state,bool limit){ if(pos==0) return 1; if(!limit && dp[pos][state]!=-1) return dp[pos][state]; int up=limit?num[pos]:9; int sum=0; for(int i=0;i<=up;i++){ if(state && i==2) continue; if(i==4) continue; sum+=dfs(pos-1,i==6,limit&&num[pos]==i); } if(!limit) dp[pos][state]=sum; return sum; } int work(int x){ cnt=0; while(x){ num[++cnt]=x%10; x/=10; } return dfs(cnt,0,1); }
|
统计区间中二进制0的个数不小于1的个数的所有数字。
这里比不要62多了一个参数,表示是否有前导零。
写法1:
三维dp,第三维表示总位数,这个地方我刚开始写错了,没考虑到这个,只记录了0的数量,其实1的数量也要考虑进去,否则1个0,1个1和1个0,2个1就会被记录成一个状态,导致答案错误,这里我这么写主要为了使用ksm剪枝,更快求出答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| int dfs(int pos,int js,bool limit,bool lead){ if(pos==0) return js>=(res-js); if(!limit && !lead && js>=(res-js)) dp[pos][js][res]=ksm(2,pos); if(!limit && !lead && dp[pos][js][res]!=-1) return dp[pos][js][res]; int up=limit?num[pos]:1; int sum=0; for(int i=0;i<=up;i++){ if(i==0 && lead){ res--; sum+=dfs(pos-1,js,limit&&num[pos]==i,1); res++; } else sum+=dfs(pos-1,i==0?js+1:js,limit&&num[pos]==i,0); } if(!limit && !lead) dp[pos][js][res]=sum; return sum; } int work(int x){ cnt=0; while(x){ num[++cnt]=x%2; x/=2; } res=cnt; return dfs(cnt,0,1,1); }
|
写法2:
当前位是0就加,是1就减,直接包含了所有状态,省空间且写法简单,需要学习,初始值为32因为不能减到负数,因为还要作为数组下标存储状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int dfs(int pos,int js,bool limit,bool lead){ if(pos==0) return js>=32; if(!limit && !lead && dp[pos][js]!=-1) return dp[pos][js]; int up=limit?num[pos]:1; int sum=0; for(int i=0;i<=up;i++){ if(i==0 && lead) sum+=dfs(pos-1,js,limit&&num[pos]==i,1); else sum+=dfs(pos-1,i==0?js+1:js-1,limit&&num[pos]==i,0); } if(!limit && !lead) dp[pos][js]=sum; return sum; } int work(int x){ cnt=0; while(x){ num[++cnt]=x%2; x/=2; } res=cnt; return dfs(cnt,32,1,1); }
|