起源:唔,,前几天打cf,edu50那场被C题虐了,决定学学数位dp。(此文持续更新至9.19)

ps:我也什么都不会遇到一些胡话大家不要喷我啊。。。

数位dp问题:就是求在区间l到r上满足规定条件的数的个数。

ex1:hdu3555

  题意:给你n,求从一到n中有多少个数不包含“49”。(t<=1e4,n<=2^63-1)

  首先数位dp顾名思义就是对数位进行dp嘛,所以dp数组的第一维我们用来保存数字的位数,第二位我们用来判定当前位是否为4,

所以就是  dp[20][2];  这个样子。 在这之前我们先考虑一下常规的搜索思路,一件非常显然的事情,在[1,1000]和[1001,2000]以及所有类似区间里符合要求的数都是一样的,这样我们就可以通过记忆化的方式来保存某些结果。

先给出solve函数

 ll solve(ll num){
int k = ;//记录数位
while(num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,false,true);
}

这个很好理解嘛,保存这个数各位上的数字。然后我们就可以进行记忆化搜索了,

dp[20][2]:表示 1.有4的时候有几个含有49, 2.没有4的时候,有几个含有49。

ll dfs(int len,bool if4,bool limit){
//当前是第几位,上一位是否是4,上一位是否是上界
if(len==0)//统计完了直接返回1
return 1;
if(!limit&&dp[len][if4])//不是上界并且这种情况已经统计过
return dp[len][if4];
ll cnt=0,up_bound=(limit?digit[len]:9);//up_bound是当前位能满足的最大值,如果上一位是上界的话,当前位最大只能取到当前位的数字,如果不是,当前位可以从0取到9
for(int i=0;i<=up_bound;i++){
if(if4&&i==9)
continue;//上一位是4并且这一位是9,GG了啊
cnt+=dfs(len-1,i==4,limit&&i==up_bound);//上一位是上界的情况下我们才会考虑这一位是否是上界
}
if(!limit)//不是上界,属于通用的情况,我们进行赋值
dp[len][if4]=cnt;
return cnt;
}
最后结果差分一下就好。 ex2:hdu2089
和上道题几乎一样,条件是没有“4”并且没有“62”,
这时候我们掏出上一道题的板子了嘛肯定要,只需要在判断时加入一句话就行,在代码中加上注释了,就不做多解释了
#include <bits/stdc++.h>
using namespace std;
int n,m;
int digit[];
int dp[][];
int dfs(int len,bool if6, bool limit){
if(len==)
return ;
if(!limit&&dp[len][if6])
return dp[len][if6];
int cnt = ,up_bound = (limit?digit[len]:);
for(int i=;i<=up_bound;i++){
if(i==)//如果遇到四就直接GG
continue;
if(if6&&i==)
continue;
cnt+=dfs(len-,i==,limit&&i==up_bound);
}
if(!limit)
dp[len][if6]=cnt;
return cnt;
}
int solve(int num){
int k = ;//记录数位
while(num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,false,true);
} int main(){
while (scanf("%d%d",&n,&m)&&(n+m)) {
cout << solve(m) - solve(n - ) << endl;
}
}

  

ex3:codeforces1036C,也就是EDU50的C题嘛,一道非常简单的板子题,(问题是我当时还没听说过数位dp,,真的,,不然就可以骑学长了,,哭)

  条件是 零的个数不大于3个。

  很显然我们只要把dp数组的第二维开到3就可以了嘛,,然后就是套板子,, 我这里把0的情况单独拿出来了,因为1到9都可以一起考虑嘛

  

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int digit[];
ll dp[][];
ll dfs(int len,int not0,bool limit){
if(len==)//完了
return ;
if(!limit&&dp[len][not0])//已经统计过
return dp[len][not0];//
ll cnt=,up_bound=(limit?digit[len]:);//
cnt+=dfs(len-,not0,limit&&digit[len]==);//
for(int i=;i<=up_bound;i++){
if(not0==)
continue;
cnt+=dfs(len-,not0+,limit&&i==up_bound);
}
if(!limit)
dp[len][not0]=cnt;
return cnt;
}
ll solve(ll num){
int k = ;//记录数位
while(num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,,true);
}
int t;ll l,r;
int main(){
ios::sync_with_stdio(false);
cin>>t;
while (t--){
cin>>l>>r;
cout<<(solve(r)-solve(l-))<<endl;
}
}

ex4:

  hdu3652

  条件:包括“13”并且能被13整除

  首先我们想到用余数来分类嘛,然后结合最初的板子,开出来的dp数组就是这样的 dp[17][17][3];//分别是数位,余数,对于“13”的三种状态(包括1,包括13,啥都没有)

  还有取模的那个地方需要稍微理解一下,剩下的也就是板子了

  

 #include <bits/stdc++.h>
using namespace std;
int digit[];
int dp[][][];
int dfs(int len,int mod,int have,int limit){
if(len==)
return mod==&&have==;
if(!limit&&dp[len][mod][have])
return dp[len][mod][have];
int cnt = ,up_bound=(limit?digit[len]:);
for(int i=;i<=up_bound;i++){
int mod_ = (mod*+i)%;
int tmp = have;
if(have==&&i==)
tmp = ;
if(have==&&i!=)
tmp = ;
if(have==&&i==)
tmp = ;
cnt+=dfs(len-,mod_,tmp,limit&&i==up_bound);
}
if(!limit)
dp[len][mod][have] = cnt;
return cnt;
} int solve(int num){
int k = ;
while (num){
k++;
digit[k] = num%;
num/=;
}
return dfs(k,,,);
}
int n;
int main(){
ios::sync_with_stdio(false);
while (scanf("%d",&n)!=EOF){
// scanf("%d",&n);
cout<<solve(n)<<endl;
}
}

ex5: codeforces 55 d,  是上一道题略微进化版   条件“这个数能被他的每个非零位的最小公倍数整除”;

首先我们还是想到对模数来分类,这是非常显然的嘛,除此之外,我们需要保存“最小公倍数”,因为转移的时候必须要用到之前的,这样子我们开出来的数组是 dp[20][2520][2520],好恭喜你MLE on test 1,这时就需要一些奇淫技巧。我们会发现,lcm的个数非常有限吧,实际上只有48个,所以我们可以离散化嘛。然后就与上一题非常相似了,我们保存 “presum”和“prelcm”,进行记忆化搜索即可。

为什么可以%2520呢,

  • 首先我们能够知道如果这个数能够整除它的每个数位上的数字,那么它一定能够整除他们的最小公倍数,是充要的。
  • 那么我们定义状态dp[i][j][k]代表i位在任意组合下,得到的所有数位的数字的最小公倍数为j,且该数%2520为k的方案数。
  • 我们可以知道任意多个1-9之间的数的公倍数最大不会超过2520,而且他们都是2520的约数,所以(一个数%2520)能够被该数所有数位的数字的最小公倍数整除,那么该数就能整除自己每个数位上的数字。
#include <cstdio>
using namespace std;
typedef long long ll;
int t;ll l,r;
const int mod = ;
ll dp[][mod][];
int ind[mod+];
int digit[];
void init(){
int cnt = ;
for(int i=;i<=mod;i++){
if(mod%i==)
ind[i]=cnt++;
}
} int gcd(int a, int b){
return b==?a:gcd(b,a%b);
} int lcm(int a, int b){
return a/gcd(a,b)*b;
}
ll dfs(int len,int presum,int prelcm,bool limit){
if(len==)
return presum%prelcm==;
if(!limit&&dp[len][presum][ind[prelcm]])
return dp[len][presum][ind[prelcm]];
ll cnt = ;int up_bound = limit?digit[len]:;
for(int i=;i<=up_bound;i++){
int nowsum = (presum*+i)%mod;
int nowlcm = prelcm;
if(i!=)
nowlcm = lcm(nowlcm,i);
cnt+=dfs(len-,nowsum,nowlcm,limit&&i==up_bound);
}
if(!limit)
dp[len][presum][ind[prelcm]] = cnt;
return cnt;
}
ll solve(ll num){
int k = ;
while (num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,,,);
} int main(){
init();
scanf("%d",&t);
while (t--){
scanf("%I64d%I64d",&l,&r);
printf("%I64d\n",solve(r)-solve(l-));
}
}

ex6:poj 3286  求区间里包含多少个零

我随手一写竟然过了。网上题解貌似没看到和我的写法一样的。所以讲的详细些,还是套板子,dfs里的四个参数分别表示 位数,零的个数,是否前导零,是否上界

然后就很简单了嘛。注意到l可以取到0,然后我们的dfs对于零来说是没有计算在内的,所以要加上1。

#include <iostream>
using namespace std;
typedef long long ll;
int digit[];
int dp[][];
ll l,r;
ll dfs(int len,int count,bool zero,bool limit){
if(len==)
return count;
if(!zero&&!limit&&dp[len][count])
return dp[len][count];
ll cnt=;int up_bound=limit?digit[len]:;
cnt+=dfs(len-,count+(zero?:),zero,limit&&digit[len]==);
for(int i=;i<=up_bound;i++){
cnt+=dfs(len-,count,false,limit&&i==up_bound);
}
if(!limit&&!zero)
dp[len][count]=cnt;
return cnt;
}
ll solve(ll num){
int k = ;
while (num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,,,);
}
int main(){
ios::sync_with_stdio(false);
while () {
cin >> l >> r;
if(l==-||r==-)
return ;
if (l == )
cout << solve(r) + << endl;
else
cout << solve(r) - solve(l - ) << endl;
}
}

ex7:poj2282&&bzoj1833&&luogu2602&&zjoi1010

求区间里包含“0,1,2,3,4,5,6,7,8,9”的个数分别是多少

和ex6一毛一样嘛,,一个很直观的思路就是我们把ex6跑十遍吧,嗯正解就是这样(囍的不行),我觉着求“1,2,3,4,5,6,7,8,9”还要比“0”简单咯,不用考虑前导0,然后把代码稍微一改就可以了。哦对了!他这个鬼输入竟然l还能大于r,所以要判断swap一下,,,我一开始测样例输出了一堆负数让我受到了很大的惊吓。。。

  

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
int digit[];
int dp[][][];
int ans[][];
ll l,r;
ll dfs0(int len,int count,bool zero,bool limit){
if(len==)
return count;
if(!zero&&!limit&&dp[len][count][])
return dp[len][count][];
ll cnt=;int up_bound=limit?digit[len]:;
cnt+=dfs0(len-,count+(zero?:),zero,limit&&digit[len]==);
for(int i=;i<=up_bound;i++){
cnt+=dfs0(len-,count,false,limit&&i==up_bound);
}
if(!limit&&!zero)
dp[len][count][]=cnt;
return cnt;
}
ll dfs(int len,int count,int num,bool limit){
if(len==)
return count;
if(!limit&&dp[len][count][num])
return dp[len][count][num];
ll cnt = ;int up_bound=limit?digit[len]:;
for(int i=;i<=up_bound;i++){
cnt+=dfs(len-,count+(i==num),num,limit&&i==up_bound);
}
if(!limit)
dp[len][count][num]=cnt;
return cnt;
}
void solve(ll num,int ind){
int k = ;
while (num){
k++;
digit[k]=num%;
num/=;
}
ans[][ind]=dfs0(k,,,);
for(int i=;i<=;i++)
ans[i][ind]=dfs(k,,i,true);
}
void init(){
memset(dp,, sizeof(dp));
memset(digit,, sizeof(digit));
}
int main(){
ios::sync_with_stdio(false);
while (cin>>l>>r&&l&&r) {
if(l>r)
swap(l,r);
init();
solve(r,);
init();
solve(l-,);
for(int i=;i<;i++)
cout<<ans[i][]-ans[i][]<<" ";
cout<<endl;
}
}

ex8:8102上海大都会赛J题

能被各位数字和整除 数据范围(N<=1e12)

  首先我们应该会求 “被1整除”,“被二整除”,“被三整除”这样子的吧,然后神妙的战法就出现了,我们可以枚举各位数字和,,,12*9=128,也就是跑100来遍就行了吧。。。

初始化要初始化成-1,因为可能有很多状态本身就没有符合条件的数。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;ll n;
int digit[];
ll dp[][][];//第i位,之前数位之和位j,对某个mod余数为k的满足条件的个数
int nowsum;
ll dfs(int len, int sum,int mod,bool limit){
if(len==)
return (sum==nowsum&&mod==);
if(!limit&&dp[len][sum][mod]!=-)
return dp[len][sum][mod];
ll cnt = ;int up_bound=limit?digit[len]:;
for(int i=;i<=up_bound;i++){
if(sum+i>nowsum)
break;
cnt+=dfs(len-,sum+i,(mod*+i)%nowsum,limit&&i==digit[len]);
}
if(!limit)
dp[len][sum][mod]=cnt;
return cnt;
}
ll solve(ll num) {
int k = ;
while (num) {
k++;
digit[k] = num % ;
num /= ;
}
ll res = ;
for(int i=;i<=*k;i++) {
nowsum = i;
memset(dp,-, sizeof(dp));
res += dfs(k,,, true);
}
return res;
} int main(){
ios::sync_with_stdio(false);
cin>>t;
int cas = ;
while (t--){
cin>>n;
cout<<"Case "<<++cas<<": "<<solve(n)<<endl;
}
return ;
}

   

re:从零开始的数位dp的更多相关文章

  1. 【BZOJ1662】[Usaco2006 Nov]Round Numbers 圆环数 数位DP

    [BZOJ1662][Usaco2006 Nov]Round Numbers 圆环数 Description 正如你所知,奶牛们没有手指以至于不能玩"石头剪刀布"来任意地决定例如谁 ...

  2. bzoj1026数位dp

    基础的数位dp 但是ce了一发,(abs难道不是cmath里的吗?改成bits/stdc++.h就过了) #include <bits/stdc++.h> using namespace ...

  3. uva12063数位dp

    辣鸡军训毁我青春!!! 因为在军训,导致很长时间都只能看书yy题目,而不能溜到机房鏼题 于是在猫大的帮助下我发现这道习题是数位dp 然后想起之前讲dp的时候一直在补作业所以没怎么写,然后就试了试 果然 ...

  4. HDU2089 不要62[数位DP]

    不要62 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  5. 数位DP GYM 100827 E Hill Number

    题目链接 题意:判断小于n的数字中,数位从高到低成上升再下降的趋势的数字的个数 分析:简单的数位DP,保存前一位的数字,注意临界点的处理,都是套路. #include <bits/stdc++. ...

  6. 数位dp总结

    由简单到稍微难点. 从网上搜了10到数位dp的题目,有几道还是很难想到的,前几道基本都是模板题,供入门用. 点开即可看题解. hdu3555 Bomb hdu3652 B-number hdu2089 ...

  7. 数位DP入门

    HDU 2089 不要62 DESC: 问l, r范围内的没有4和相邻62的数有多少个. #include <stdio.h> #include <string.h> #inc ...

  8. 数位DP之奥义

    恩是的没错数位DP的奥义就是一个简练的dfs模板 int dfs(int position, int condition, bool boundary) { ) return (condition ? ...

  9. 浅谈数位DP

    在了解数位dp之前,先来看一个问题: 例1.求a~b中不包含49的数的个数. 0 < a.b < 2*10^9 注意到n的数据范围非常大,暴力求解是不可能的,考虑dp,如果直接记录下数字, ...

随机推荐

  1. Column 'u_id' in field list is ambiguous

  2. 基于SOUI开发一个简单的小工具

    基于DriectUI有很多库,比如 Duilib (免费) soui (免费) DuiVision (免费) 炫彩 (界面库免费,UI设计器付费,不提供源码) skinui (免费使用,但不开放源码, ...

  3. getParameter() getInputStream()和getReader() 区别 这三种方法是有冲突的,因为流只能被读一次。

    我们经常用servlet和jsp, 经常用request.getParameter() 来得到数据. request.getParameter()request.getInputStream()req ...

  4. 查看当前正在运行的python进程

    ps -ef |grep Python kill -9 pid  

  5. Elasticsearch冷热集群搭建

    ES版本:6.2.4 集群环境:7台机器,每台部署一个master节点.其中3台部署2个hot节点,另外4台部署2个warm节点.共21个节点. 1. 挂盘 按实际情况分盘,一个机子上的2个data节 ...

  6. 修改Electron的libcc(libchromiumcontent)源码,重新编译electron, 设置event.isTrusted为true

    VPN非常注意: 编译的过程需要使用VPN, 否者chromium的源代码无法下载, 后面会出现总总问题 Electron的编译环境, 推荐使用物理机: win10 64位 英文版, 为了避免后期出现 ...

  7. (原)faster rcnn的tensorflow代码的理解

    转载请注明出处: https://www.cnblogs.com/darkknightzh/p/10043864.html 参考网址: 论文:https://arxiv.org/abs/1506.01 ...

  8. linux驱动(续)

    网络通信 --> IO多路复用之select.poll.epoll详解 IO多路复用之select.poll.epoll详解      目前支持I/O多路复用的系统调用有 select,psel ...

  9. maven scope使用和理解

    在Maven的依赖管理中,经常会用到依赖的scope设置.这里整理下各种scope的使用场景和说明,以及在使用中的实践心得. scope的使用场景和说明 1.compile 编译范围,默认scope, ...

  10. Apigee 简介与简单试用

     Apigee (国内访问需要***)是一家成立于2004年的API管理公司,于2016年9月被Google收购,作为Google云的服务之一.Apigee提供从API设计.开发.管理.门户.网关等 ...