数位DP
数位DP
核心思想
当答案和某个数的每一位之间有强相关联系时,可以以每一个位置作为阶段来DP。
P2602 数字计数
给定两个正整数 \(a,b\) , 求在 \([a,b]\) 中每个数码出现了多少次。
暂且不考虑前导零,那么对于满 \(i\) 位的数:
- 所有数字的出现次数相同
设数组 \(dp_i\) 为满 \(i\) 位数中每个数字出现的次数,那么就可以有转移:
\(dp_i=10\times dp_{i-1}+10^{i-1}\)
然后需要考虑怎么处理(这部分较简单(差分,对每个数单独求),直接跳过)。
还需要单独考虑前导零怎么处理,第 \(i\) 位为前导零时,此时 \(1\) 到 \(i-1\) 位都是 \(0\) , 额外减去 \(i-1\) 位填满的贡献。
P13085 windy数
不含前导零且相邻两个数字之差至少为 \(2\) 的正整数被称为 windy 数。求在 \(a\) 和 \(b\) 之间,包括 \(a\) 和 \(b\),总共有多少个 windy 数?
设 \(f[i][j]\) 表示前 \(i\) 位,最后一位为 \(j\) 的方案数,发现非常好求。
接着考虑怎么做,
仍然是前导零非常难处理。
设要处理的数字 \(x\) 的位数为 \(len\) ,先处理位数 \(<len\) 的数,可以直接求出来。
接下来处理位数刚好等于 \(len\) 的情形。
那么 \(for\ i:len-1\)
第 \(i\) 个循环用来求解前 \(i\) 位都与 \(x\) 的前 \(i\) 位相同的方案数。
第一位不能为 \(0\) 其余可以。
就做完了:
ll solve(ll x){
for (int i=1;i<len;i++){
for (int j=1;j<=9;j++){
ans+=f[i][j];
}
}
int last=-2;//首位都可以选
for (int i=len-1;i>=0;i--){
int num=x/arr[i];//当前数字
int st;
if (i==len-1) st=1;
else st=0;//首位不能选0,其余可以
if (i==0){//最后一位只用考虑自己
for (int j=st;j<num;j++){
if (abs(j-last)<2) continue;
ans++;
}
if (abs(last-num)<2){
flag=false;
break;
}
continue;
}
for (int j=st;j<num;j++) {
if (abs(j-last)<2) continue;
for (int k=0;k<=9;k++) {
if (abs(j-k)<2) continue;
ans+=f[i][k];
}
}
if (abs(last-num)<2){
flag=false;
break;
}//这说明前i位不能做到紧贴上限,那么后面都不用继续循环了
last=num;
x%=arr[i];
}
return ans;
}
阶段总结:
由此注意到递推的方法中非常难处理前导0,由此改用记忆化搜索版本。
这种版本非常好写好想(尤其是在前导0的处理上),且不用初始化。
记忆化搜索版本:
\(dfs(int\ pos,int\ last,bool\ limit,bool\ lead)\) 表示当前处理的是 \(pos\) 位,上一位是 \(last\) , 是否紧贴上限,前面是否是前导零。
具体直接处理就行了。
ll dp[20][22][2][2];
ll arr[22];//每一位的数
ll dfs(int pos,int last,bool limit,bool lead){
if (pos==0) return lead?0:1;
if (dp[pos][last][limit][lead]!=-1){
return dp[pos][last][limit][lead];//记忆化
}
ll ans=0;
ll up=limit?arr[pos]:9;//如果前面紧贴那么最多取到arr[pos],否则可以最多取到9
for (int i=0;i<=up;i++){
if (abs(last-i)<2) continue;
int if_lead=lead&&(i==0);//当前还是不是前导零
int if_limit=limit&&(i==arr[pos]);//当前还有没有限制
if (if_lead){
ans+=dfs(pos-1,20,if_limit,1);
}
else{
ans+=dfs(pos-1,i,if_limit,0);
}
}
return dp[pos][last][limit][lead]=ans;
}
ll solve(ll x){
memset(dp,-1,sizeof dp);
arr[0]=0;
while(x){
arr[++arr[0]]=x%10;
x/=10;
}//获取每一位的数以及初始化dp
return dfs(arr[0],20,1,1);
}
int main(){
cin >> a >> b;
cout << solve(b)-solve(a-1);
return 0;
}

浙公网安备 33010602011771号