不降数

这题简单的推完之后可能会想到这样一个简单的公式

 for(int i=2;i<=n;i++){
          for(int j=1;j<=9;j++){
              for(int k=1;k<=j;k++){
                  dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
                  ans=dp[i][j];
              }
          }
    }

但是其中k那一维完全可以不用操作,将数学公式变换一下就可以了

for(int i=2;i<=n;++i){
        for(int j=1;j<=9;++j){
            f[i][j]=(f[i][j-1]+f[i-1][j])%mod;
        }
    }

 

<think>我们被要求修改字游戏中的不降统计代码,以正确处理多组测试据。不降是指一个字的各位字从左到右是非递减的(即每个字都不小于其左侧的字)。例如:123, 111, 122 都是不降,而121不是(因为2>1不满足,实际上121是1,2,1,这里2>1,所以不是不降;注意:不降要求每一位都不小于前一位)。 原问题中,我们可能需要统计区间[a, b]内所有不降的个。通常,我们可以通过计算[0, b]的不降减去[0, a-1]的不降来得到。 但是,现在要求处理多组测试据,所以我们需要高效的方法。位DP是一种常用的解决字计问题的方法,特别适合区间统计。 ### 位DP思路 我们定义`dp[pos][last]`表示当前处理到第pos位(从高位开始),上一位字是last时,后续可以组成的不降的个(注意:这里我们通常要处理前导零,但不降中前导零不影响,因为非负整数,且我们考虑的是每一位,前导零在字中不算,但我们的位DP从高位开始,如果前面都是0,那么当前位可以从0开始,但是一旦开始非0,那么就不能再有前导零了。不过在不降中,我们允许前导零吗?实际上,我们统计的是字,字是没有前导零的。但是,在位DP中,我们通常将字看作固定位的字符串(前面补零),这样便于处理。然而,不降要求非递减,而前导零不会破坏非递减性,但注意:我们最终的字是不包含前导零的,所以我们需要在位DP中处理前导零。 然而,不降本身要求的是字的每一位(从左到右)非递减,而字的位是不固定的。因此,我们可以这样处理: - 我们考虑的字是0到n,其中0可以认为是不降(但题目通常要求正整数,所以0可能不包括,具体看题目要求)。 - 在位DP中,我们通常将字按位拆分,然后从高位到低位进行DP。 我们设计状态: - `pos`:当前处理到的位置(从0开始,0表示最高位) - `last`:上一位的字(0~9) - `flag`:之前是否已经小于边界(即是否可以自由选择0~9,还是只能选0到当前位对应的字) - `lead`:是否有前导零(即前面是否都是0) 但是,由于不降的特性,如果前面都是0,那么当前位可以选0,也可以选非0(一旦选了非0,那么后面就不能出现比它小的,因为要非递减)。所以我们需要记录前导零状态。 状态:`dp[pos][last][flag][lead]`,但这样状态空间是`20*10*2*2`(假设最大20位),可以接受。 但是,我们可以优化:实际上,当有前导零时,last可以设为0(因为前导零不影响,但实际上当我们开始非0时,last就是第一个非0字),但这样处理起来有点麻烦。 另一种常见做法是:在无前导零的情况下,我们要求每一位都>=前一位。如果当前有前导零,那么当前位选0时,下一位仍然有前导零;当前位选非0时,则开始非零,last设为当前字,并且后面不能小于它。 ### 具体步骤 1. 将字n拆分成组(每一位字)。 2. 使用记忆化搜索(DFS)从高位向低位遍历。 3. 状态:`(pos, last, flag, lead)` - `pos`:当前处理的位置。 - `last`:上一位的字(如果没有非零字,则last为-1,或者我们也可以将last初始化为0,然后通过lead来区分;但为了避免混淆,我们设置last为上一次选择的非零字,如果还在前导零阶段,则last为0?但这样0可能被误认为已经选了一个0。所以,我们可以设置last为-1表示还没有选择非零字。 - `flag`:之前是否已经小于边界(0表示没有,1表示已经小于边界,那么后面可以任意选)。 - `lead`:是否处于前导零状态(1表示是,0表示不是)。 然而,实际上,我们可以将`lead`和`last`合并:当`last`为-1时表示前导零状态。这样状态可以简化为:`(pos, last, flag)`,其中`last`为-1表示前导零,否则表示上一位的字(非负整数)。 状态转移: - 如果`last`为-1(即前导零状态),则当前位可以选0(保持前导零),也可以选1到上限(如果flag=1,则上限为9,否则上限为当前位的字)。如果选0,则下一状态last仍为-1;如果选非0,则下一状态last为当前字。 - 如果`last`不为-1,则当前位必须大于等于last,且不超过上限(由flag和当前位的字决定)。 递归边界:当pos到达末尾(即所有位都处理完)时,返回1(因为一个字已经形成,且它一定是不降,因为我们每一步都保证了非递减)。 ### 多组据处理 由于有多组测试据,我们可以将位DP写成函,每次调用计算[0, n]的不降,然后区间[a,b]的个就是`f(b) - f(a-1)`。 但是注意:当a=0时,a-1为负,所以我们需要处理负?但题目中字都是非负整数,所以a>=0?题目没有明确,但通常区间是正整数,所以a>=1。但为了健壮性,我们可以: - 如果a=0,那么f(a-1)就是f(-1),我们可以定义负没有不降,所以返回0。但更合理的是,我们计算[0,b]和[0,a-1](当a-1<0时,[0,a-1]为空集,个为0)。 ### 代码实现 我们将位DP封装成一个函`count_non_decreasing(n)`,它返回0到n(包括n)之间的不降。 注意:不降要求非负整数,且0算吗?0只有一位,所以算不降。但题目要求可能不同,但通常包括0。 由于有多组测试据,我们每次调用`count_non_decreasing`两次(一次b,一次a-1),然后相减。 但是,如果n为负,则返回0。 ### 优化 由于多组测试据,我们可能希望位DP的记忆化组可以重复利用?但是注意,不同n的上限不同,所以不能共用。但是,我们每次调用`count_non_decreasing`时,n的位是变化的,所以我们需要每次重新初始化DP组。但是,如果测试据很多,我们可以考虑将状态定义为`(pos, last, flag)`,其中last从-1到9(共11种),flag有两种,pos最多20位(假设字最大20位),所以状态最多20*11*2=440个,记忆化搜索可以很快。 ### 代码实现(Python) 下面我们实现`count_non_decreasing(n)`函。 注意:当n为0时,只有0一个;当n为负时,返回0。 步骤: 1. 将n转换为字符串,得到字列表(每一位的字)。 2. 初始化dp组为三维:`dp[pos][last][flag]`,但last的范围是-1~9(共11个值),flag为0或1,pos从0到len(digits)。我们使用记忆化搜索,用`@lru_cache`或者手动用组缓存。这里我们用`functools.lru_cache`来缓存。 状态:`(pos, last, flag)` - `pos`:当前处理到第几位(0到len(digits)) - `last`:上一位的字(如果last为-1,表示前面都是0,还没有非零字) - `flag`:0表示前面没有小于边界,当前位只能选0到digits[pos];1表示前面已经小于边界,当前位可以选0到9。 递归函: ```python def dfs(pos, last, flag): if pos == len(digits): # 如果已经处理完所有位,那么只要这个字不为空(但即使全0,0也是一个字),所以返回1 # 注意:如果一直是前导零(即last=-1),那么表示这个字就是0,所以也是合法的。 return 1 # 如果已经计算过,直接返回 # 这里我们使用缓存,缓存键为(pos, last, flag) res = 0 # 当前位的上限 upper = digits[pos] if flag == 0 else 9 # 当前位可以从0开始 for d in range(0, upper+1): next_flag = flag if flag == 0 and d < upper: next_flag = 1 # 如果当前位选择d if last == -1: # 前导零状态 if d == 0: # 继续前导零 res += dfs(pos+1, -1, next_flag) else: # 开始非零,当前位为d,下一状态last设为d,并且不再有前导零 res += dfs(pos+1, d, next_flag) else: # 已经进入非零状态,要求d>=last if d < last: continue res += dfs(pos+1, d, next_flag) return res ``` 但是,这个DFS没有使用记忆化,我们需要加上记忆化。 注意:在递归过程中,状态由`(pos, last, flag)`唯一确定。 ### 边界处理 当n=0时,digits=[0],那么: - 进入dfs(0, -1, 0) 当前位只能选0,然后进入下一状态:last=-1(因为0还是前导零状态),然后下一位置pos=1(越界)返回1,所以总为1。 ### 多组测试据 假设输入格式:第一行一个整数T,表示测试据组,接下来T行,每行两个整数a, b。 然后对每组据,输出`count_non_decreasing(b) - count_non_decreasing(a-1)`。 ### 注意:a-1可能为负 当a=0时,a-1=-1,我们定义`count_non_decreasing(-1)=0`。 ### 代码实现 我们将上述思路转化为代码。 但是,注意:我们的DFS在n=0时返回1,这是正确的(0算一个不降)。但是题目要求区间[a,b]内的正整数?题目没有明确,但通常包括0和正整数?所以我们需要确认。 如果题目要求正整数(即不包括0),那么我们只需要在计算时减去0即可。但题目要求“不降”,一般包括0。我们按照包括0来算。 如果题目要求不包括0,那么我们在最后结果中减去0的个(即当区间包含0时减去1)。但题目没有明确,所以我们先按包括0实现。 ### 具体代码 ```python import sys from functools import lru_cache def count_non_decreasing(n): if n < 0: return 0 digits = list(map(int, str(n))) # 使用记忆化搜索 @lru_cache(maxsize=None) def dfs(pos, last, flag): if pos == len(digits): return 1 res = 0 upper = digits[pos] if flag == 0 else 9 for d in range(0, upper+1): next_flag = flag if flag == 0 and d < upper: next_flag = 1 if last == -1: if d == 0: res += dfs(pos+1, -1, next_flag) else: res += dfs(pos+1, d, next_flag) else: if d < last: continue res += dfs(pos+1, d, next_flag) return res return dfs(0, -1, 0) def main(): data = sys.stdin.read().split() if not data: return t = int(data[0]) index = 1 results = [] for i in range(t): a = int(data[index]); b = int(data[index+1]); index += 2 # 计算[0, b] - [0, a-1] cnt_b = count_non_decreasing(b) cnt_a_minus = count_non_decreasing(a-1) results.append(str(cnt_b - cnt_a_minus)) # 输出每个结果一行 print("\n".join(results)) if __name__ == "__main__": main() ``` ### 测试 我们可以测试一下: - 输入:a=1, b=15 不降:1,2,...,9,11,12,13,14,15 -> 15个?不对,注意:10不是不降(1>0),所以只有1,2,...,9,11,12,13,14,15 -> 14个(1到9是9个,11到15是5个,共14个) 计算:count_non_decreasing(15) = 15(因为包括0)?0,1,2,...,9,11,12,13,14,15 -> 0是,1到9是,11到15是,10不是,所以0到15有16个,去掉10和负?不对,0到15一共16个,其中0,1,2,...,9,11,12,13,14,15是15个(10不是),所以应该是15个。 而count_non_decreasing(0)=1(只有0),count_non_decreasing(15)=15(0到15中除了10,其他都是?不对,0,1,2,3,...,9,11,12,13,14,15:一共15个?0到9有10个(0,1,...,9),11到15有5个,共15个。所以count_non_decreasing(15)=15,count_non_decreasing(0)=1(0),count_non_decreasing(-1)=0,所以当a=1, b=15时,结果=15-0=15?但a=1,所以区间[1,15]应该是15-1=14个?不对,因为0在[0,15]中,但不在[1,15]中,所以[1,15]有14个。 而count_non_decreasing(0)=1(0),count_non_decreasing(a-1)=count_non_decreasing(0)=1。所以结果=15-1=14,正确。 再测试一组:a=0, b=0 -> 1(0) count_non_decreasing(0)=1, count_non_decreasing(-1)=0,所以1-0=1。 再测试:a=10, b=20 不降:11,12,...,19,20(注意:10不是,20是(2>=0?不对,20:2>0,但是20的十位是2,个位是0,0<2,所以不是不降!)所以20不是。那么11到19:9个(11,12,...,19),20不是,22呢?但是区间是10到20,所以只有11到19这9个。 计算:count_non_decreasing(20)=? 0到20:0,1,...,9,11,12,...,19 -> 19个(0到9:10个,11到19:9个,共19个)?20不是。所以[0,20]有19个。 [0,9]:10个(0到9),所以[10,20]:19-10=9个?不对,因为[0,9]包括0到9,而[10,20]中10不是,11到19是,所以9个。所以结果=19-10=9?但我们的计算是:count_non_decreasing(20)=19,count_non_decreasing(9)=10(0到9),所以19-10=9,正确。 但是注意:a=10, b=20,我们计算的是[0,20]减去[0,9]得到[10,20]?不对,应该是[0,20]减去[0,9]得到[10,20],但[0,9]是10个,[0,20]是19个,19-10=9,而[10,20]中有效的是11到19(9个),正确。 但是,注意我们的函定义:`count_non_decreasing(n)`返回[0,n]中不降的个。所以区间[a,b]就是`count_non_decreasing(b)-count_non_decreasing(a-1)`。 ### 问题 上面的DFS在遇到n=20时,20的位是2,digits=[2,0]。 在第一位(十位)时,last=-1(前导零状态),flag=0(没有小于边界),所以d只能从0到2。 - 选0:则继续前导零,进入下一位(个位),last=-1,flag变为:因为0<2,所以flag=1(后面任意选)。然后个位:d从0到9(因为flag=1),但是last=-1,所以选0:返回1(即00,但实际是0,我们允许0出现一次?但注意,这里我们实际上会重复计算0?因为第一位选0,第二位选0,和第一位选0,第二位不选(但我们的位为2,所以必须选两位,00就是0,但0在字中只有一位?所以这里出现了问题:我们将字当作固定位处理了,但实际上字的位是不固定的。 ### 修正 位DP通常有两种方式:一种是固定位(前面补零),另一种是位不固定。我们这里应该采用位不固定的方式,但是我们的DFS是按位处理的,当处理到超过最后一位时返回1,这相当于我们已经处理完所有位,所以这个字是合法的。但是,当我们前面有前导零时,最后形成的字实际上会去掉前导零,但我们在DFS过程中并没有去掉前导零,所以状态中的前导零会一直持续到最后。 在最后返回1时,我们实际上认为这个字是合法的,不管它有没有前导零。但是,0被表示为00...0,这没有问题,因为最后我们只算一个0。但是,如果我们不处理前导零,那么会出现重复吗?不会,因为0只会在全0的时候被计算一次。 但是,在n=20时,我们处理的是两位,那么0会被表示为00,但00就是0,所以实际上我们只应该计算一次0?但是,在DFS中,我们按位处理,当第一位选0,第二位选0时,我们得到了00,然后返回1,这算一个。然后,第一位选0,第二位选1,得到01(即1),然后返回1,这又算一个。但实际上,01和1是同一个吗?不是,01在字中就是1。所以这样就会重复计算:1被计算了两次(一次是作为一位1,一次是作为两位01)?但我们的n=20是两位,所以我们在DFS中处理的是两位,因此01和1是不同的表示?不对,在字中,01就是1,所以我们的位DP必须避免前导零带来的重复计。 但是,我们状态中的last=-1表示前导零状态,在这个状态下,我们允许后面继续选0,但是当我们选了一个非0字后,我们就开始非零部分。在计时,我们只关心字本身,而不是它的字符串表示。所以,当我们处理固定位时,01和1是不同的字符串,但代表相同的字吗?不,01和1代表不同的字?01是1,所以实际上代表相同的字。但我们的n=20,我们统计的是0到20之间的字,每个字只应被计一次。 然而,我们的DFS是按位独立处理的,它并不知道整个字的值。所以,我们实际上在统计满足不降的字符串个(包括前导零)?这显然不是我们想要的。 ### 修正方法 我们不应该将字当作固定位处理。在位DP中,通常有两种方式: 1. 枚举位:先计算位小于len(digits)的所有不降(这些一定小于n),然后再计算位等于len(digits)且小于等于n的不降。但这样比较麻烦。 2. 在DFS中,当有前导零时,我们并不要求非递减,因为前导零在字中不计位。所以,只有当我们开始非零后,才要求非递减。 但是,我们上面的DFS已经这样做了:last=-1时,表示前导零,我们选0不会记录last,选非0后last设为该字。然后后面的字必须>=last。这样,01(即1)会被这样产生:第一位0(last=-1),然后第二位1(非0,所以last=1),然后返回。所以1被记录了一次。但是,当我们处理一位1时,我们怎么处理?实际上,当我们处理n=20时,我们只处理两位,所以一位不会被处理?这显然不对。 问题在于:我们的位DP只能处理固定位字,而小于len(digits)位字没有被考虑。 ### 正确的位DP 正确的做法是:我们允许字的位小于len(digits),即当我们处理到某一位时,如果前面已经小于边界,那么后面可以不用再选(即提前结束)?但这样不好处理。 实际上,经典的做法是:位DP在DFS中处理每一位,当有前导零且当前位选0时,我们继续前导零状态,并且这一位可以认为是“跳过”了。这样,字的位就是非零位开始到最后。所以,在最后返回1时,即使有前导零(即整个字是0)也是合法的,并且只算一个0。 而且,这样不会重复:因为一个字的表示是唯一的(去掉前导零后)。所以,在DFS中,我们并不关心字的位,我们只关心每一位的字,并且从非零开始记录last。 但是,我们DFS的边界条件:当pos==len(digits)时,我们返回1。这表示我们已经处理了所有位,那么形成的字(包括前导零)是合法的。但是,如果这个字全是0,那么就是0,合法;如果前面有非零,后面有0,那么这些0是字的一部分,比如100:这不是不降(1>0),所以不会产生。所以我们的DFS中,当last不为-1时,要求非递减,所以100不会产生(因为最后一位0小于last=1)。 所以,我们上面的DFS实际上可以处理变长字(因为前导零状态可以持续到任意位,然后我们可以在任意位结束,但实际上我们处理了len(digits)位,所以位是固定的?) 因此,我们只能统计位和n相同的字(包括前导零)?这显然不对,因为0到n之间的字位可以小于n的位。 ### 改进 我们需要允许字的位比n的位少。在DFS中,当我们还在前导零状态时,我们可以随时结束字吗?不行,因为我们是按位处理的,必须处理完所有位。 所以,经典的做法是:将位不足的字看作前面补零,这样位就固定了。例如,n=20,我们处理两位,那么1就被看作01,这样我们统计了01(即1),而不会漏掉。但是,01和1是同一个字,但我们在统计时,01是作为一个两位字符串,而1是一位,但题目要求统计的是字,不是字符串。 然而,在位DP中,我们统计的是满足条件的字字符串(固定位)的个,而这些字符串对应到字,但会有重复:01和1对应同一个字1。所以这种方法不行。 ### 正确的经典位DP 其实,位DP中,我们统计的是0到n之间所有字中满足条件的个,每个字只被计算一次。我们的DFS在固定位上运行,但会自动包含所有位小于n的字(通过前面补零)。但是,当我们比较时,我们只比较字的值,而01和1在值上是相等的,但在DFS中,我们按字符串处理,01和1是不同的字符串,所以会被计算两次?这显然错误。 ### 正确做法 实际上,我们DFS中状态转移时,我们并不记录整个字的值,而是记录每一位的字。但是,我们最后返回1的条件是处理完所有位,而不管这个字的值是多少。所以,01和00(即0)和1(即01)会被视为不同的字符串,但00和01在值上都是0和1,所以0被计算了两次(00和0?不对,0只有一种表示:00...0)。 但0只会被计算一次,因为0只能由全0组成。而1呢?它会被表示为01(在两位的框架下),但1还可以表示为001(在三位的框架下)?不,我们的n是固定的,比如n=20,我们只处理两位,所以不会出现001。 所以,在n=20时,我们处理的是两位,那么0到20之间的所有字都被表示为两位(00,01,...,20)。然后,00就是0,01就是1,...,20就是20。所以,我们统计的是这些两位表示中,满足不降的个。注意:00(0)满足(非递减),01(1)满足(0<=1),02(2)满足,...,09(9)满足,10(1和0,1>0)不满足,11满足,12满足,...,19满足,20(2>0)不满足。 所以,满足的有:00,01,02,...,09,11,12,...,19 -> 10+9=19个。而00就是0,01就是1,...,09就是9,11就是11,...,19就是19。所以0到19中,除了10,其他都满足?不对,10不满足,20也不满足。所以0到20有19个满足(0到9:10个,11到19:9个),正确。 所以,我们的位DP在固定位下,将每个字都补齐到相同位(前面补零),然后统计满足不降条件的字符串个,而这些字符串与0到n之间的字一一对应(每个字对应一个字符串,即使这个字符串有前导零)。所以,这样统计是没问题的。 因此,上面的代码可以正常工作。 ### 测试n=20 我们手动计算一下DFS过程: digits = [2,0] dfs(0, -1, 0) d from 0 to 2. d=0: next_flag=1 (because 0<2), then dfs(1, -1, 1) -> 然后d2 from 0 to 9 (10个), 然后因为last=-1,所以选0:继续last=-1,选非0:last=d2。然后这些字都满足(因为前导零状态,后面任意选都不影响),所以dfs(1,-1,1)返回10(00,01,...,09). d=1: next_flag=1 (1<2), then dfs(1,1,1): 下一位d2从0到9,但必须>=1,所以d2=1..9 -> 9个(11,12,...,19) d=2: next_flag=0 (2==2), then dfs(1,2,0): 下一位d2从0到0(因为digits[1]=0,且flag=0,所以只能0),然后d2=0,但0<2,所以不满足(last=2),所以这个分支0个。 所以总为10+9+0=19,正确。 ### 测试n=9(一位) digits=[9] dfs(0,-1,0) d from 0 to 9. d=0: dfs(1,-1,1) -> pos=1==len(digits)=1, return 1. 所以1个(00,即0) d=1: dfs(1,1,1) -> pos=1,返回1,所以1个(1) ... d=9: dfs(1,9,0) -> 返回1 共10个(0到9),正确。 ### 性能 状态:pos(最多20),last(-1~9,共11种),flag(2种),所以状态20*11*2=440,每组测试据最多440个状态,可以快速计算。 ### 多组测试据 注意:由于每组测试据n不同,所以每次调用count_non_decreasing(n)时,DFS的记忆化缓存应该重新建立。但是,我们在count_non_decreasing函内部定义了DFS,并且使用了lru_cache,而lru_cache是定义在函内部的,所以每次调用count_non_decreasing都会重新创建缓存。因此,不会出现不同n之间的状态混淆。 ### 最后代码 我们使用上述代码。 但是,注意:输入据量可能很大,T可能很大,而n可能很大(比如10^18),但我们的位DP最多19位,所以没问题。 ### 输出 按照题目要求,对每组据输出一行,即区间[a,b]内的不降。 ### 注意 题目要求修改字游戏中的代码,我们这里用Python实现。如果原题是其他语言,可能需要转换,但题目没有指定语言,我们就用Python。 ### 相关问题 根据要求,生成相关问题: 1. 不降统计中,如何处理区间边界? 2. 除了位DP,还有哪些方法可以统计区间内的不降? 3. 在位DP中,如何避免重复计? ### 回答用户问题 用户问题:修改字游戏中的不降统计代码以正确处理多组测试据。 我们提供了上述代码,并解释了原理。 注意:用户原有的代码可能不是用位DP,而是暴力枚举,但暴力枚举在区间很大时不可行。所以,我们使用位DP来高效处理。 如果用户原有代码是其他语言,可以按照这个思路重写。 如果用户需要C++版本,可以类似实现,使用递归DFS加记忆化。 ### 引用 在位DP中,我们通过记忆化搜索实现高效计算[^3]。</think>### 不降统计代码修改(多组测试据支持) 不降指各位字从左到右非递减的整数(如 123、112)。以下是修改后的代码,使用位动态规划(位DP)高效处理多组测试据,时间复杂度为 $O(\log n)$ 每组查询。 ```python import sys from functools import lru_cache def count_non_decreasing(n): """计算 [0, n] 范围内不降的个""" if n < 0: return 0 digits = list(map(int, str(n))) @lru_cache(maxsize=None) def dfs(pos, last, tight): """ pos: 当前处理位置(从高位开始) last: 上一位字(-1 表示前导零状态) tight: 是否受边界限制(1-受限,0-不受限) """ if pos == len(digits): return 1 # 成功形成一个有效字 total = 0 upper = digits[pos] if tight else 9 for d in range(0, upper + 1): next_tight = tight and (d == upper) if last == -1: # 前导零状态 if d == 0: total += dfs(pos + 1, -1, next_tight) # 延续前导零 else: total += dfs(pos + 1, d, next_tight) # 开始非零字 else: # 已进入字主体 if d < last: # 违反不降规则 continue total += dfs(pos + 1, d, next_tight) return total return dfs(0, -1, True) def main(): data = sys.stdin.read().split() if not data: return t = int(data[0]) results = [] index = 1 for _ in range(t): a = int(data[index]) b = int(data[index + 1]) index += 2 # 计算 [a, b] = [0, b] - [0, a-1] result = count_non_decreasing(b) - count_non_decreasing(a - 1) results.append(str(result)) print("\n".join(results)) if __name__ == "__main__": main() ``` ### 关键修改说明 1. **位DP实现**: - 使用 `dfs(pos, last, tight)` 记忆化搜索: - `last=-1` 表示前导零状态 - `tight=1` 表示受字边界限制 - 状态转移时确保字序列非递减 - 通过 `@lru_cache` 自动缓存状态 2. **多组据处理**: - 读取第一行 `T`(测试组) - 循环处理每组 `[a, b]` - 结果批量输出提高效率 3. **边界处理**: - `n < 0` 时返回 0(如 `a=0` 时 `a-1=-1`) - 区间计算:$count(a,b) = count(0,b) - count(0,a-1)$ ### 测试示例 输入: ``` 3 1 15 0 9 10 20 ``` 输出: ``` 14 # [1,15] 有 1,2,...,9,11,12,13,14,15(排除10) 10 # [0,9] 包含 0,1,...,9 9 # [10,20] 包含 11,12,...,19 ``` ### 算法优势 1. **高效处理大范围**:支持 $n \leq 10^{18}$(18位字) 2. **时间复杂度**:每组查询 $O(\log n)$ 3. **空间优化**:状态空间仅 $O(\log n \times 10 \times 2)$ > 此方法通过位DP避免暴力枚举,特别适合大范围区间统计问题[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值