数位dp
转载:https://www.cnblogs.com/zbtrs/p/6106783.html
一.求a~b中不包含49的数的个数. 0 < a、b < 2*10^9
我们要求[a,b]不包含49的数的个数,可以想到利用前缀和来做,具体来说,就是[a,b] = [0,b] - [0,a),(")"是不包括a),我们先求出给定a,b的每个位置的数,保存在数组s中,例如a = 109,那么a[1] = 9,a[2] = 0,a[3] = 1.然后开始dp,我们可以选择记忆化搜索或者是递推,前一种相对于第二种而言简单和较为容易理解一些,所以我们选择记忆化搜索.那么需要记录些什么呢?首先长度是一定要记录的,然后记录当前的数位是否为4,这样就便于在记忆化搜索中得到答案.
然后进行记忆化搜索,记录上一位是否为4和枚举这一位,如果没有限制的话很好办,直接枚举就可以了,但是这样可能会超空间,因此我们每次都必须要判断是否有最大的限制
#include <bits/stdc++.h> using namespace std; int a,b; int shu[20],dp[20][2]; // if4是记录上一位是否为4 int dfs(int len,bool if4,bool shangxian) { if(len == 0) { return 1; } if( !shangxian && dp[len][if4] ){ return dp[len][if4]; } int cnt = 0,maxx = (shangxian ? shu[len]:9); for(int i=0;i<=maxx;i++) { if(if4 && i==9) continue; cnt += dfs(len-1, i==4 ,shangxian && i == maxx); } return shangxian ? cnt : dp[len][if4] = cnt; } int solve(int x) { memset(shu,0,sizeof(shu)); int k = 0; while(x) { shu[++k] = x%10; x /= 10; } return dfs( k , false , true ); } int main() { scanf("%d %d",&a,&b); cout<<solve(b) - solve(a-1)<<endl; return 0; }
二.求a~b中不包含62和4的数的个数. 0 < a、b < 2*10^9
分析:和上一题一样,只需要再判断一下4是否出现和上一位是否为6即可.
#include <bits/stdc++.h> using namespace std; int a,b; int shu[20],dp[20][2]; int dfs(int len,bool if6,bool shangxian) { if(len == 0) { return 1; } //shangxian == false && if( !shangxian && dp[len][if6] ){ return dp[len][if6]; } int cnt = 0,maxx = (shangxian ? shu[len]:9); for(int i=0;i<=maxx;i++) { if(i== 4|| (if6 && i==2)) continue; cnt += dfs(len-1, i==6 ,shangxian && i == maxx); } return shangxian ? cnt : dp[len][if6] = cnt; } int solve(int x) { memset(shu,0,sizeof(shu)); int k = 0; while(x) { shu[++k] = x%10; x /= 10; } return dfs( k , false , true ); } int main() { scanf("%d %d",&a,&b); cout<<solve(b) - solve(a-1)<<endl; return 0; } //shu 数组里存放的就是个int类型的数 小于二十位的一个超大的数
三.windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,
在A和B之间,包括A和B,总共有多少个windy数?
#include <bits/stdc++.h> using namespace std; int a,b,shu[20],dp[20][12]; int dfs(int len,int last,bool shangxian) { int p; if(len == 0){ return 1; } if(!shangxian && dp[len][last] != -1 && last >= 0){ return dp[len][last]; } int cnt = 0,maxx = (shangxian ? shu[len] : 9); for(int i=0;i<=maxx;i++) { if(abs(i - last) < 2) continue; p = i; if(i == 0 && last == -10) p = last; cnt += dfs( len - 1 , p , shangxian && (i == maxx) ); } if(last >= 0 && !shangxian) dp[len][last] = cnt; return cnt; } int solve(int x) { int k = 0; while(x) { shu[++k] = x%10; x /= 10; } memset(dp,255,sizeof(dp)); return dfs(k , -10 , true); } int main() { cin>>a>>b; cout<<solve(b) - solve(a-1)<<endl; return 0; }
四.找出1~n范围内含有13并且能被13整除的数字的个数.
分析:和例1相比多了一个整除,怎么处理呢?其实只需要在记忆化搜索中增加一个参数mod即可,利用(a * b) % mod = (a % mod) * (b % mod)和(a + b) % mod = (a % mod) + (b % mod)来计算.比如说73 % 10 = ((7 % 10) * 10 + 3) % 10,但要注意本题是要找含有13的数,那么在处理的时候就要进行分类讨论.
#include <bits/stdc++.h> using namespace std; int n; int shu[20],dp[20][20][2]; int dfs(int len,int mod,int zhuangtai,int shangxian) { if(len == 0) return mod==0&&zhuangtai == 2; if(!shangxian &&dp[len][mod][zhuangtai]) { return dp[len][mod][zhuangtai]; } int cnt = 0 ,maxx =(shangxian ? shu[len]:9); for(int i=0;i <= maxx;i++) { int tz = zhuangtai; if(zhuangtai != 2&&i != -1) tz = 0; if(zhuangtai == 1&& i == 3) tz = 2; if(i==1 && zhuangtai != 2) tz = 1; cnt += dfs(len - 1,(mod*10 +i)%13,tz,shangxian&&i == maxx); } if(!shangxian) dp[len][mod][shangxian] = cnt ; return cnt; } int main() { while(scanf("%d",&n)){ memset(shu,0,sizeof(shu)); memset(dp,0,sizeof(dp)); int k = 0; while(n) { shu[++k] = n%10; n/=10; } cout<<dfs(k,0,0,1)<<endl; } return 0; }
五.求区间[a,b]中的数转化为二进制后0比1多的数的个数.
分析:典型的数位dp题,先在二进制上做dp,最后转化到十进制上.求出[0,b]和[0,a-1]的答案,相减就可以了.
一个坑点:二进制数必须要存在,也就是说必须要有一个1开头.
#include <bits/stdc++.h> using namespace std; #define ll long long ll shu[100],a,b; ll dp[100][100][100]; ll dfs(ll len,ll num1,ll num0,bool shangxian,bool can) { if(len == 0) { if(num0 >= num1) return 1; return 0; } if(!shangxian && dp[len][num1][num0]) return dp[len][num1][num0]; ll cnt = 0, k = (shangxian ? shu[len]:1); for(int i=0;i<=k;i++) { if(i==0) { if(can) cnt += dfs(len - 1,num1,num0+1,(shangxian && i==k),can || i == 1); else cnt += dfs(len - 1,num1,num0 ,(shangxian && i==k),can || i == 1); } if(i==1) { cnt += dfs(len-1 , num1+1,num0,(shangxian && i==k),can ||i == 1); } } if(!shangxian) { return dp[len][num1][num0] = cnt; } else return cnt ; } ll solve(ll x) { memset(shu,0,sizeof(shu)); ll k = 0; while(x) { shu[++k] = x%2; x/=2; } return dfs(k,0,0,true,false); } int main() { cin>>a>>b; cout<<solve(b) - solve(a-1)<<endl; return 0; }
总结 数位dp模板
1.如果题目中出现求满足区间[l,r]的符合......性质的数的个数,考虑使用数位dp.
2.思考一下:如果我们只能从前往后一位位枚举当前的数位,要做出这道题,我们需要知道哪些量?利用这些来补充到dfs的调用参数中.
3.套用模板.
#include <bits/stdc++.h> using namespace std; #define ll long long ll dp[100][100][1010]; ll shu[100]; ll dfs(int len, , , bool shangxian) { if(len == 0) return ...; if(!shangxian && dp[len][ ][ ] ) return dp[len][ ][ ]; ll cnt = 0; int maxx = (shangxian ? shu[len] : 9); for(int i=0;i<=maxx ;i++) { ...; cnt += dfs(len - 1, , , shangxian && i==maxx) } if(!shangxian) dp[len][ ][ ] = cnt; return cnt; } ll solve(ll x) { int k = 0; while(x) { shu[++k] = x%10; x /= 10; } return dfs(k, , ,1); } int main() { memset(dp,0,sizeof(dp)); cin>>a>>b; cout<<solve(b) - solve(a-1)<<endl; return 0; }