数位DP Increasing or Decreasing

本文介绍了一种使用数位DP解决区间内单调递增或递减整数计数问题的方法,通过预处理和动态规划技巧高效计算指定区间的momonumber数量。

Increasing or Decreasing

We all like monotonic things, and solved many problems about that like Longest Increasing Subsequence

(LIS). Here is another one which is easier than LIS (in my opinion).
We say an integer is a momo number if its decimal representation is monotonic. For example, 123, 321,777 and 5566 are momo numbers; But 514, 50216 and 120908 are not.
Please answer m queries. The i-th query is a interval [li, ri], and please calculate the number of momo numbers in it.
Input


The first line contains an integer m.
Each of the following m lines contains two integers li , ri .


• 1 ≤ m ≤ 105
• 1 ≤ li ≤ ri ≤ 1018


Output


For each query, please output the number of momo numbers in that range.
Sample input 


2
1 100
100 200


Sample output
100

48


题目大意:

定义一类momo number:各数位从高位到低位单调不减或者单调不增,给出L,R,问区间[L,R]之间momo number的数量。

我的做法是用数位DP统计从0到R,L-1的momo number,再相减得到答案。

至于如何得到0到N,我是先统计以x结尾长度为y的单调不升的方案数,和以x结尾长度为y的单调不降的方案数,然后先获得位数比当前数字位数低的momo number的数量,再从高位依次枚举到低位,枚举当前位的数字统计momo number的数量,统计方法是依次固定前几个高位的数字再枚举当前位的数字值就可以不重不漏的统计,但是要注意的是,如果前面固定的数字出现了先上升再下降或者先下降再上升的情况,就立即退出统计。

代码:

#include <bits/stdc++.h>
using namespace std;

inline void read(int &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}


inline void read(long long  &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

const int maxn=30;
int fdown[maxn][11];
int fup[maxn][11];

void prepare(){
for (int i=0;i<=9;i++)
    {
        fdown[1][i]=1;
        fup[1][i]=1;
    }
for (int i=1;i<=20;i++)
    for (int j=0;j<=9;j++)
        for (int k=j;k<=9;k++)
            fdown[i][j]+=fdown[ i-1 ][k];
for (int i=1;i<=20;i++)
    for (int j=0;j<=9;j++)
        for (int k=0;k<=j;k++)
            fup[i][j]+=fup[i-1][k];
}

int query(long long x){
if (x<=10)
    return x+1;
int num[20];
int cnt=0;
long long tmp=1;
while (x)
    {
        num[++cnt]=x%10;
        x=x/10;
        tmp=tmp*10;
    }
reverse(num+1,num+cnt+1);
int ans=0;
ans=query(tmp/10-1);
int op=0;
bool is=1;
for (int i=1;i<=cnt;i++)
    {

        bool vis1=0,vis2=0;
        if (op!=1)
        if (i!=1)
            {
                for (int j=0;j <=min( num[i-1] , num[i]-1) ;j++)
                {
                    ans+=fup[cnt-i+1][j];
                    vis1=1;
                }
            }
        else
            for (int j=1;j < num[i];j++)
            {
                ans+=fup[cnt-i+1][j];
                vis1=1;
            }
        //printf("down  %d\n",ans);

        if (op!=-1)
        if (i!=1)
            {
                for (int j=num[i-1];j < num[i] ;j++)
                {
                    //printf("%d %d\n",cnt-i+1,fdown[cnt-i+1][j]);
                    ans+=fdown[cnt-i+1][j];
                    vis2=1;
                }
            }
        else
            for (int j=1;j < num[i]; j++)
            {
                ans+=fdown[cnt-i+1][j];
                //printf("%d %d\n",cnt-i+1,fdown[cnt-i+1][j]);
                vis2=1;
            }

        //printf("up  %d\n",ans);
        if (op==0)
            {
                if (i!=1)
                {
                    if ( num[i] < num[i-1])
                        op=-1;
                    if ( num[i] > num[i-1])
                        op=1;
                }
            }
        if (i==1)
            ans-=num[i]-1;
        else
            if ( vis1 && vis2)
                ans--;
        if (op==1)
            {
                if ( num[i] < num[i-1] )
                {
                    is=0;
                    break;
                }
            }
        if (op==-1)
            {
                if ( num[i] > num[i-1] )
                {
                    is=0;
                    break;
                }
            }
        //printf("      %d\n",ans);
    }
if (is)
    ans++;
return ans;
}


int main(){
    //freopen("mine.out","w",stdout);
    prepare();
    //printf("%d\n",query(212));
    //puts("\n");
    //printf("%d\n",query(211));
    //int N=1000000;
    //for (int i=0;i<=N;i++)
        //printf("%d\n",query(i));
    int T;
    read(T);
    for (int i=1;i<=T;i++)
        {
            long long l,r;
            read(l); read(r);
            printf("%d\n",query(r)-query(l-1));
        }
    return 0;
}



题目描述: 某公园有N(3≤N≤50)棵树排成一排,已知每棵树的高度。现要去掉一些树,使得剩下树的高度从左至右呈现先递增再递减的规律(即剩余的树中仅有一棵最高的树,且它左侧的所有树中后一棵树都要比前一棵树高,它右侧的所有树中后一棵树都要比前一棵树矮) 给出N棵树的高度(高度单位:m,1.0≤每棵树高度≤100.0,保留一位小数),请你计算出最少去掉几棵树才能使这排树呈现先递增再递减的规律,如果不能呈现则输出-1(只有递增或者只有递减都为不能呈现)。 例如:N=10,10棵树的高度从左到右依次为1.0、2.3、1.2、1.7、1.1、2.0、1.8、1.8、1.2、1.9。 要使这排树呈现先递增再递减的规律,最少去掉4棵树,去掉的编号分别为2、5、8、10。 剩余树的高度依次为1.0、1.2、1.7、2.0、1.8、1.2,最高树为2.0,其左侧树的高度依次为1.0、1.2、1.7、2.0,呈现递增趋势(从左至右且包含最高树);其右侧树的高度依次为2.0、1.8、1.2,呈现递减趋势(从左至右且包含最高树)。 输入描述: 第一行输入一个正整数N(3≤N≤50),表示这排树的数量 第二行输入N个数(1.0≤每个数≤100.0,保留一位小数),表示每棵树的高度,每个数之间以一个空格隔开 输出描述: 输出一个整数,表示最少去掉几棵树才能使这排树呈现先递增再递减的规律,如果不能呈现则输出-1 样例输入: 10 1.0 2.3 1.2 1.7 1.1 2.0 1.8 1.8 1.2 1.9 样例输出: 4 帮我用传统的方式做出来不需要很复杂的函数和知识点,并且逻辑需要简单明了易懂c++代码
最新发布
08-08
### 数位递增的概念 数位递增是指一个正整数中的每一位数字都不大于其右侧相邻的数字。换句话说,对于任意位置 \(i\) 的数字 \(d_i\) 和其右侧相邻的位置 \(j\) 的数字 \(d_j\) (其中 \(i < j\)),满足条件 \(d_i \leq d_j\)。例如,1135 是一个数位递增的数,因为它的每一位都小于等于下一位;而 1024 不是数位递增的数,因为在第二位上出现了较大的数字。 这种定义可以通过逐位比较来验证一个数是否属于数位递增类别[^3]。 --- ### 实现方法与算法 #### 方法一:字符串处理方式 通过将数字转换为字符串形式逐一比较字符大小,可以判断该数是否为数位递增的数。以下是 Python 中的一种实现: ```python def count_increasing_numbers(n): ans = 0 for number in range(1, n + 1): # 遍历从 1 到 n 的所有数字 s = str(number) # 将当前数字转为字符串 flag = True # 假设当前数字符合条件 for j in range(1, len(s)): if s[j - 1] > s[j]: # 如果前一位大于后一位,则不符合条件 flag = False break # 提前退出循环 if flag: # 若标志仍为真,则计数加一 ans += 1 return ans # 返回最终的结果 if __name__ == "__main__": n = int(input()) result = count_increasing_numbers(n) print(result) ``` 这种方法的时间复杂度主要取决于输入范围内的遍历次数以及每次内部的逐位对比操作,总体时间复杂度大约为 O(k * log₁₀n),其中 k 表示总共有多少个数需要被检测,log₁₀n 表示平均每个数所含有的位数。 --- #### 方法二:动态规划优化 另一种更高效的解决方案涉及动态规划的思想。我们可以预先计算出不同长度下的可能组合数目,并利用这些预计算结果快速得出答案。具体来说,假设 dp[l][k] 表示长度为 l 并且最高位不超过 k 的有效数位递增数量,则状态转移方程如下所示: \[ dp[l][k] = \sum_{m=0}^{k}{dp[l-1][m]} \] 初始条件设置为当长度为 1 时,任何单个数字都是有效的数位递增数。因此有: \[ dp[1][k] = k+1,\quad 对于所有的 k \in [0..9] \] 最后统计范围内所有合法数值即可得到总数目。此方法能够显著减少重复运算量,在大规模数据集上的表现优于简单枚举法[^1]。 --- #### Java 实现最长连续递增子序列 虽然题目询问的是关于数位递增的具体概念及其应用,但这里也提供一段基于数组寻找最长严格单调上升子串的代码作为补充说明,这有助于理解如何高效地判定一系列元素之间的相对顺序关系: ```java class Solution { public int findLengthOfLCIS(int[] nums) { if (nums.length == 0) return 0; int max = 0; int num = 1; for (int i = 0; i < nums.length - 1; i++) { if (nums[i] < nums[i + 1]) { num++; } else { if (max < num) max = num; num = 1; } } return Math.max(num, max); } } ``` 上述代码片段展示了如何在一个整型数组中找到最长连续递增子序列的长度。尽管它并不直接解决原问题,但它体现了类似的逻辑思维过程——即通过对前后项之间差值或者大小关系进行评估从而完成特定目标的任务[^2]。 --- ### 总结 综上所述,无论是采用简单的暴力枚举还是复杂的动态规划策略都可以有效地解决问题。选择哪种技术路线应视具体情况而定,比如性能需求、开发周期等因素都会影响决策方向。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值