数位dp_从模板到理解
来源:互联网 发布:淘宝手持照片怎么拍 编辑:程序博客网 时间:2024/06/05 07:14
dp是啥,我觉得dp就是记忆化的递推(递归).不同的dp的区别就在于递推(递归)的方式.
而数位dp,我觉得简单来讲就是从上一位数字推出下一位数字的状态.
在开始写数位dp之前,我推荐http://blog.csdn.net/wust_zzwh/article/details/52100392这位大佬的blog,写的真的非常非常好,看完理解它,刷刷题,数位dp就可以说是大成了.
这道题大概就这些.这题比较简单,不用套模板,自己理解着写就ok了.
CodeForces - 55D
题意:输入n,求1到n有多少个数是b数~一个数能整除所有组成它的非0数字
这个题就可以套用内个模板了
首先先知道2520是1到9的最小公倍数,然后
数位dp先写这么多,后面再有我再继续写
ps:人生第一篇blog哇...
而数位dp,我觉得简单来讲就是从上一位数字推出下一位数字的状态.
在开始写数位dp之前,我推荐http://blog.csdn.net/wust_zzwh/article/details/52100392这位大佬的blog,写的真的非常非常好,看完理解它,刷刷题,数位dp就可以说是大成了.
先贴个大佬的模板,数位dp就从这个模板开始.
int a[20]; ll dp[20][state];//不同题目状态不同 ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零 { //递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了 if(pos==-1) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */ //第二个就是记忆化(在此前可能不同题目还能有一些剪枝) if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state]; /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/ int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了 ll ans=0; //开始计数 for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了 { if() ... else if()... ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的 /*这里还算比较灵活,不过做几个题就觉得这里也是套路了 大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论 去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目 要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类, 前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/ } //计算完,记录状态 if(!limit && !lead) dp[pos][state]=ans; /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/ return ans; } ll solve(ll x) { int pos=0; while(x)//把数位都分解出来 { a[pos++]=x%10;//个人老是喜欢编号为[0,pos),看不惯的就按自己习惯来,反正注意数位边界就行 x/=10; } return dfs(pos-1/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛 } int main() { ll le,ri; while(~scanf("%lld%lld",&le,&ri)) { //初始化dp数组为-1 printf("%lld\n",solve(ri)-solve(le-1)); } }
看了这个模板,大部分人就明白了吧.
数位dp实际上就是个记忆化dfs,再判断一下是否是上界就ok了.
HDU - 2089
数位dp大家的第一题都是no62吧.
中文题就不写题意了.
这个题就是让你理解什么叫做数位dp而已.
实际上就是先求出所有范围内的dp可能,再求我的要求的区间的dp值.
dp[位][这一位的值] = 答案
#include<cstdio>#include<cstring>#include<algorithm>using namespace std;typedef long long ll;ll f[10000][10];void getdp(){f[0][0] = 1;for(int i = 1;i < 10;i++){for(int j = 0;j < 10;j++){if(j == 4){f[i][j] = 0;}else{if(j == 6){for(int k = 0;k < 10;k++){f[i][j] += f[i - 1][k];}f[i][j] -= f[i - 1][2];}else{for(int k = 0;k < 10;k++){f[i][j] += f[i - 1][k];}}}}}return;}int a[10000];int solve(int n){a[0] = 0;while(n){a[ ++a[0] ] = n % 10;n /= 10;}a[a[0] + 1]=0;ll ans=0;for (int i = a[0];i >= 1;i--) {for (int j = 0;j < a[i];j++){if (j != 4 && !(a[i + 1] == 6 && j == 2))ans += f[i][j];}if (a[i] == 4){break;}if (a[i + 1] == 6 && a[i] == 2){break;}}return ans;}int main(){int l,r;scanf("%d%d",&l,&r);//int l = 1;//int r = 100;getdp();while(l != 0 && r != 0){ll ans1 = solve(r+1);ll ans2 = solve(l);printf("%lld\n",ans1 - ans2);scanf("%d%d",&l,&r);}return 0;}
这道题大概就这些.这题比较简单,不用套模板,自己理解着写就ok了.
CodeForces - 55D
题意:输入n,求1到n有多少个数是b数~一个数能整除所有组成它的非0数字
这个题就可以套用内个模板了
首先先知道2520是1到9的最小公倍数,然后
sum%(x*n)%x == sum%x
那我们就假设一个数,RX(R是已知前面位取余后的值),那么RX%2520 == (R*10+x)%2520
这样我们就得到了数位dp最重要的位置的递推关系.
len表示迭代的长度,
num为截止当前位的数对2520取余后的值。
lcm为截止当前位的所有数的最小公倍数。
flag表示当前数是否可以任意取值(对取值上限进行判断)**
则可用dp[len][num][lcm]来对其进行记录。
然后还要注意一点,就是这道题需要离散
lcm按2520取值根本开不下,所以对lcm进行离散化,因为lcm一定可以整除2520,所以将1~2520可以整除2520的数进行标记即可
#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#include<cstdlib>#include<cmath>#include<vector>#include<functional>using namespace std;int MOD = 2520;int lcm[50];int lcmid[2555];long long dp[20][50][2555];int a[30];int gcd(int a, int b) { return b ? gcd(b, a % b) : a;}long long dfs(int pos, int res, int lcms, int limit) { if (pos == 0) return res % lcms == 0; long long &ans = dp[pos][lcmid[lcms]][res]; if (!limit && ans != -1) return ans; int top = limit ? a[pos] : 9; long long cnt = 0; for (int i = 0; i <= top; i++) { //printf("dfs(%d,%d,%d,%d)\n",pos,res,lcms,limit); int now = lcms; if (i != 0) now = lcms * i / gcd(lcms, i); int nres = (res * 10 + i) % MOD; cnt += dfs(pos - 1, nres, now, limit && i == top); } if (!limit) ans = cnt; return cnt;}long long slove(long long A) { if (A < 0) return 0; int pos = 0; while (A) { a[++pos] = A % 10; A /= 10; } long long ans = 0; return dfs(pos, 0, 1, 1);}int main() { int c = 0; memset(dp, -1, sizeof dp); for (int i = 1; i <= MOD; i++) { if (MOD % i == 0) { lcm[c] = i; lcmid[i] = c++; } } int T; scanf("%d", &T); while (T--) { long long l, r; cin >> l >> r; cout << slove(r) - slove(l - 1) << endl; }}
数位dp先写这么多,后面再有我再继续写
ps:人生第一篇blog哇...
阅读全文
0 0
- 数位dp_从模板到理解
- 数位dp_小结
- 数位dp总结 之 从入门到模板
- 【模板&2个套路】数位DP 从入门到放弃
- 数位dp总结 之 从入门到模板
- 数位dp总结 之 从入门到模板
- 数位dp总结 之 从入门到模板
- 数字绘画技术,从数位板到数位屏
- 模板,从服务端到客户端
- 从模板模式到JdbcTemplate
- 进击的DP----数位DP入门到理解
- 带你从模板理解树链剖分
- 数位dp模板
- 数位DP模板
- 数位DP模板
- 数位DP模板
- 【自用模板】数位dp
- 数位DP模板
- SQL使用DOS命令建库。建表,添加约束,标量值函数,存储过程,触发器,游标
- org.apache.commons.codec Hex.encodeHexString(Byte[] bytes)byte[]转16进制字符串
- 行级触发器的创建和使用
- 继承
- 清除浮动的9种方法
- 数位dp_从模板到理解
- Qt利用ui制作登录界面
- caffe mean_file变成mean_value
- 【安全牛学习笔记】kali的安装与配置
- jsoncpp中获取key的方法
- Oracle 获取本周、本月、本季、本年的第一天和最后一天 (咋个办呢 zgbn)
- ADO.NET 访问数据库(二)
- 编写一个学生类Students,该类成员变量包括学号no、姓名name、性别sex和年龄age
- 挪摊啦~