Acwing 1082 数字游戏 [数位DP详解+模板]
数位 DP 问题往往都是这样的题型,给定一个闭区间([L,R]),让你求这个区间中满足某种条件的数的总数。
前缀和思想,转化为(f([0,R])-f([0,L-1]))求解。
转化成求(f(N)),将上限N转化成10进制(根据题意转化为K进制,一般是十进制),枚举从最高位开始枚举N的10进制的每一位,只要该位的取值小于N的10进制对应位的取值,那么这个数必然小于等于N。
一般使用DFS求解。
inline int dfs(int pos,int pre,bool limit,bool lead)
一般要记录当前在处理哪一位,当前位前一位的相关信息,当前位是否有上限限制,当前位是否有前导0限制(最高位不能取0)
//limit向下一位传递的条件
limit==1&&i==a[pos] //当前位有限制,并且当前位取到了限制下的最大值
//lead向下一位传递的条件
lead==1&&i==0 //当前位有前导0限制(当前位不能取0),并且当前位取的就是0
例题 Acwing 1082 数字游戏
题意
求[X,Y]中的数,满足数字从左到右非下降关系的数的个数,如:123,445.
思路
数位dp经典题。
前缀和思想,转化为(f([0,R])-f([0,L-1]))求解。
转化成求(f(N)),将上限N转化成10进制(根据题意转化为K进制,一般是十进制),枚举从最高位开始枚举N的10进制的每一位,只要该位的取值小于N的10进制对应位的取值,那么这个数必然小于等于N。
代码
#include <bits/stdc++.h>
using namespace std;
//f[i][j]表示前i位,第i+1位为j时的满足条件的数的总数
//同时充当标记数组,DFS使用记忆化搜索,一般将f[][]初始化为-1,递归过程中如果出现f[i][j]!=-1,表示这个状态已经搜索过,直接返回答案
//本题只需要记录一个值(数的个数),如果一个状态需要记录多个值,f数组定义为结构体
int f[12][12];
int a[12];//用于存储上限N的K进制下的每个数
inline int dfs(int pos,int pre,bool limit){//pos表示当前处理的位,per记录前一位的信息,这里是记录前一位的值,limit表示当前位是否有上限的限制
if(!pos)return 1;//递归出口
if(!limit&&f[pos][pre]!=-1)return f[pos][pre];//当前位没有限制,并且已经搜索过,直接返回答案
int up=limit?a[pos]:9;//获取当前位的取值上限,如果有限制,上限为n的对应位的值,否则上限为9
int res=0;
for(int i=0;i<=up;++i){//枚举当前位的取值
if(i<pre)continue;//由于要满足不下降数的限制,如果当前位的取值比上一位小,当前位取值不合法,没有必要处理当前位取i时后面位的取值
//当前位取值合法,递归枚举下一位的取值,下一位上限有限制的条件是limit==1&&a==a[pos](也就是说当前位有限制,并且当前位取了上限值。
//比如123,百位取1,百位首先肯定是有限制的,因为百位最大取1,此时该位也取到了上限值1,那么下一位十位也是有限制的,不能超过2)。而如果当前位取0,那么十位就能随便取值了0-9都能取。
res+=dfs(pos-1,i,limit&&i==a[pos]);
}
//记忆化搜索,只有在当前状态没有任何限制的时候(没有最高位限制和前导0限制),才表示这个状态可以通用。
//return (limit||lead)?res:f[pos][pre]=res;
return limit?res:f[pos][pre]=res;
}
inline int sol(int n){
if(!n)return 1;//特殊处理n==0的情况
memset(f,-1,sizeof f);//记忆化数组兼标记数组初始化为-1,有些状态可能本来就是0,所以初始化为-1
//获取n的10进制下的每一位
int pos=0;
while (n)a[++pos]=n%10,n/=10;
return dfs(pos,-1,true);
}
int main(){
int l,r;
while (cin>>l>>r){
cout<<sol(r)-sol(l-1)<<endl;//转化为前缀和求解
}
return 0;
}