数位DP由来
先来一道题
传送门
给出一个闭区间,求闭区间里的round number数(只要该数字二进制中0的个数≥1的个数,就是round number)
而\(2^{31}\)就可以等于2e9了,所以很容易可以得到以下代码
#include
#include
#define ll long long
using namespace std;
int check(ll x){
ll a=0,b=0;
while(x){
if(x%2==0)a++;
else b++;
x/=2;
}
return a>=b?1:0;
}
int main(){
ll s,f;
while(cin>>s>>f){
ll ans=0;
for(ll i=s;i<=f;i++){
if(check(i)){
ans++;
}
}
printf("%lld\n",ans);
}
return 0;
}
然而,光荣地til了
其实呢,可以看到计算一个数字的时间复杂度是O(31)也就是O(1)
但是计算的是一个区间,复杂度达到了O(2e9)
数位DP
数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数。所谓数位dp,字面意思就是在数位上进行dp咯。数位还算是比较好听的名字,数位的含义:一个数有个位、十位、百位、千位......数的每一位就是数位啦!
所以dp开的大小就可以是几位数,dp[i] 就表示小于等于 \(10^{i}\) 的数中
之所以要引入数位的概念完全就是为了dp。数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。
一般应用于:
- 求出在给定区间 [A,B] 内,符合条件 P(i) 的数 i 的个数.
- 条件 P(i) 一般与 数 的大小无关,而与 数的组成 有关.
先引入一个简单的题
传送门
求[1,1e10]中数字没有62或4的数的个数
定义dp[i][j]表示位数为i,最高位为j的时,符合题意的个数
dp[1][2]=1表示一位,且最高位为2,就是2,符合的情况有1个,就是2
dp[2][0]=9表示二位,且最高位是0,就是0-9,符合情况的有9个
dp[2][6]=8表示二位,且最高位是6,就是60-69,符合情况的有8个
比如位数为2时,dp[2][j]+=dp[1][k],把0-9的情况都加上去
比如位数为3时,dp[3][j]+=dp[2][k],把10-99的情况都加上
比如位数为4时,dp[4][j]+=dp[3][k],把100-999的情况都加上
if(j==4){//最高位为4
dp[i][j]=0;
}else if(j==6){//最高位为6
for(int k=0;k<=9;k++){
if(k!=2)dp[i][j]+=dp[i-1][k];//如果个位为2,不变,如果不为2,最高位变成之前的一位,最高位进行枚举
}
}else{//其他情况
for(int k=0;k<=9;k++){
do[i][j]+=dp[i-1][k];
}
}