数位DP 学习笔记
板子题
出处
escription
蒟蒻hzwer NOIP2014惨跪,他依稀记得他的准考证号是37,现在hzwer又将要面临一场比赛,他希望准考证号不出现37(连续),同时他又十分讨厌4,所以也不希望4出现在准考证号中。。。现在他想知道在A和B之间有多少合法的准考证号
Input
包含两个整数,A B
Output
一个整数。
Sample Input
「输入样例一」
1 10
「输入样例二」
25 50
Sample Output
「输出样例一」
9
「输出样例二」
14
「数据规模和约定」
\(20\%\) 的数据,满足 \(1 \le A \le B \le 1000000\) 。
\(100\%\) 的数据,满足 \(1 \le A \le B \le 2000000000\) 。
算法解析
因为不要求输出路径,而且不能贪心,那么考虑DP。
但是我们发现DP的状态很难设计,这里就要用到数位DP来解决这个问题了。
数位DP把一个数字拆开成若干位,如下图,把每一位当做一个节点,也就是说,把每一位当做一个独立的节点,然后设计方程式。一般方程状态是位数和当前位的状态。
数位DP一般有两种计算方法,一种是记忆化搜索(递归),另一种是递推。因为一般给出条件的时候不是位数,而是具体的一个数字。
我们发现,如果要用记搜的话,就要从高到低进行扫描;递推则从低到高。
题目解析
令 \(\def\foo{\operatorname{f}} \foo(x,y,op)\) 表示从高到低第 \(x\) 位,前一位为 \(y\) ,如果 \(op=1\) 则之前的数字取的是最大值,否则不是的答案。
递推式显然。
算法复杂度为 \(\Theta\left(10N\right)\) ,\(N\) 为位数, \(10\) 就是指总共每位有 \(10\) 个数字。
代码
(此代码防抄袭)
#include<cstdio>
#include<cstring>
#define maxn 39
using namespace std;
#define debug
typedef int Type;
inline Type read(){
Type sum=0;
int flag=0;
char c=getchar();
while((c<'0'||c>'9')&&c!='-') c=getchar();
if(c=='-') c=getchar(),flag=1;
while('0'<=c&&c<='9'){
sum=(sum<<1)+(sum<<3)+(c^48);
c=getchar();
}
if(flag) return -sum;
return sum;
}
int from,to;
int tmp[maxn],n;
int dp[maxn][maxn][2];
void swap(int &x,int &y){ x^=y^=x^=y; }
int f(int x,int y,int op){
if(dp[x][y][op]) return dp[x][y][op];
if(x==n+1) return dp[x][y][op]=1;
int cnt=0;
if(op){
for(int i=0;i<=tmp[x]-1;i++)
if(i!=4&&(y!=3||i!=7))
cnt+=f(x+1,i,0);
if(tmp[x]!=4&&(y!=3||tmp[x]!=7))
cnt+=f(x+1,tmp[x],1);
}
else{
for(int i=0;i<=9;i++)
if(i!=4&&(y!=3||i!=7))
cnt+=f(x+1,i,0);
}
return dp[x][y][op]=cnt;
}
int getans(int x){
n=0;
while(x){
tmp[++n]=x%10;
x/=10;
}
memset(dp,0,sizeof(dp));
for(int i=1;i<=(n>>1);i++)
swap(tmp[i],tmp[n-i+1]);
return f(1,tmp[1],1);
}
int main(){
freopen("1.in","r",stdin);
freopen("my.out","w",stdout);
#ifdef debug
to=read(); printf("%d",getans(to));
#else
from=read(); to=read();
printf("%d",getans(to)-getans(from-1));
#endif
return 0;
}
推荐练习
[ZJOI2010]数字计数
[SCOI2009] windy 数
又可以水很多蓝题和紫题了