数位dp
例题
数位dp实质是记忆化搜索
比如给出一个区间[l,r]
要你求[l,r]中有多少个数是3的倍数
for(int i=l;i<=r;i++){
if(i%3==0) ans++;
}
但是这样时间复杂度为o(r-l-1)接近于O(n),但如果判断的条件比较复杂,很容易超时
所以我们考虑数位dp
什么是数位dp
数位dp是对每个数位单独考虑,枚举可能的状态,记录一个答案,在结尾进行记录,方便记忆化,然后return 就行了
ll dfs(int dep,bool limit,int a,int b,int c,int d){
if(dep==0) return 1;
if(!limit&&dp[dep][a][b][c][d]!=-1) return dp[dep][a][b][c][d];
int up=limit?num[dep]:9;
ll res=0;
for(int i=0;i<=up;i++){
if(d==10&&i==0) res+=dfs(dep-1,limit&&i==up,10,10,10,10);
else
{
if(K==2){
if(i==d) continue;
res+=dfs(dep-1,limit&&i==up,b,c,d,i);
}
if(K==3){
if(i==d||i==c) continue;
res+=dfs(dep-1,limit&&i==up,b,c,d,i);
}
if(K==4){
if(i==b||i==c||i==d) continue;
res+=dfs(dep-1,limit&&i==up,b,c,d,i);
}
if(K==5){
if(i==a||i==b||i==c||i==d) continue;
res+=dfs(dep-1,limit&&i==up,b,c,d,i);
}
}
}
if(!limit){
dp[dep][a][b][c][d]=res;
}
return res;
}
具体每道题不一样,要根据题目写状态
注意:
- 记忆化搜索一定要加!limit,因为有上限和无上限方案数是不同的,例如问100~123有多少个数(举例子而已,具体不会有这样的题),如果第二位是1,则发现最后一位有4种填法,更新dp[1][state]=4( state是题目条件,这里只是举例,并没有条件)但当第二位是2的时候并没有4种填法,造成矛盾
- 有些题目要判断前导0,比如问100~200之间有多少个每个数位上的数字都不同的数(即123,152合法,111,112,221非法),因为不判前导0的话会把0也当数位,(即102是非法的,因为枚举的时候是0102),这时通常用10代替前导0
if(d==10&&i==0) res+=dfs(dep-1,limit&&i==up,10,10,10,10);
- 一般习惯从 [ 1 , p o s ] [1,pos] [1,pos]位,但网上许多题解会从 [ 0 p o s − 1 ] [0~pos-1] [0 pos−1],有所区别
- 一般数位dp要开 long long
答案的套路: s o l v e ( r ) − s o l v e ( l − 1 ) solve(r)-solve(l-1) solve(r)−solve(l−1)