# Acwing 1082 数字游戏

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;
}
原文地址:https://www.cnblogs.com/sstealer/p/13298912.html