BNUOJ 52325 Increasing or Decreasing 数位dp

本文解析了BNUOJ52325问题,通过数位DP方法求解区间内非递增和非递减序列的数量。讨论了重复序列的处理方法,并给出了一种避免重复计算的有效策略。
传送门:BNUOJ 52325 Increasing or Decreasing
题意:求[l,r]非递增和非递减序列的个数
思路:数位dp,dp[pos][pre][status]
  1. pos:处理到第几位
  2. pre:前一位是什么
  3. status:是否有前导零

递增递减差不多思路,不过他们计算的过程中像5555,444 这样的重复串会多算,所以要剪掉。个数是(pos-1)*9+digit[最高位],比如一位重复子串是:1,2,3,4...9,9个,二位重复子串:11,22,33,44,...,99,9个;同理,其他类推;

不过这个题如果dp值每算完一个[l,r]就清零,会超时。那么我们这么分析,算[l1,r1],[l2,r2]这两个区间时,dp是否真的有必要清零呢,答案是否定的,记忆化搜索的过程中记录的dp值如果计算过,那么当其他值算到他时,这个值是可以用的。具体的自己想想就好了

/**************************************************************
    Problem:BNUOJ 52325 Increasing or Decreasing
    User: youmi
    Language: C++
    Result: Accepted
    Time:    380 ms
    Memory:    1632 KB
****************************************************************/
//#pragma comment(linker, "/STACK:1024000000,1024000000")
//#include<bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <cmath>
#include <queue>
#include <deque>
#include <string>
#include <vector>
#define zeros(a) memset(a,0,sizeof(a))
#define ones(a) memset(a,-1,sizeof(a))
#define sc(a) scanf("%d",&a)
#define sc2(a,b) scanf("%d%d",&a,&b)
#define sc3(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define scs(a) scanf("%s",a)
#define sclld(a) scanf("%lld",&a)
#define pt(a) printf("%d\n",a)
#define ptlld(a) printf("%lld\n",a)
#define rep(i,from,to) for(int i=from;i<=to;i++)
#define irep(i,to,from) for(int i=to;i>=from;i--)
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
#define lson (step<<1)
#define rson (lson+1)
#define eps 1e-6
#define oo 0x3fffffff
#define TEST cout<<"*************************"<<endl
const double pi=4*atan(1.0);

using namespace std;
typedef long long ll;
template <class T> inline void read(T &n)
{
    char c; int flag = 1;
    for (c = getchar(); !(c >= '0' && c <= '9' || c == '-'); c = getchar()); if (c == '-') flag = -1, n = 0; else n = c - '0';
    for (c = getchar(); c >= '0' && c <= '9'; c = getchar()) n = n * 10 + c - '0'; n *= flag;
}
ll Pow(ll base, ll n, ll mo)
{
    ll res=1;
    while(n)
    {
        if(n&1)
            res=res*base%mo;
        n>>=1;
        base=base*base%mo;
    }
    return res;
}
//***************************

int n;
const int maxn=100000+10;
const ll mod=1000000007;
int digit[30];
ll dp0[20][20][2];
ll dp1[20][20][2];
int tot=0;
ll dfs0(int pos,int pre,int status,int limit)
{
    if(pos<0)
        return status;
    if(!limit&&dp0[pos][pre][status]!=-1)
        return dp0[pos][pre][status];
    int ed=limit?digit[pos]:9;
    ll res=0;
    if(status==0)
    {
        for(int i=0;i<=min(pre,ed);i++)
        {
            if(i==0)
                res+=dfs0(pos-1,10,0,limit&&(i==ed));
            else
                res+=dfs0(pos-1,i,1,limit&&(i==ed));
        }
    }
    else
    {
        for(int i=0;i<=min(ed,pre);i++)
            res+=dfs0(pos-1,i,status,limit&&(i==ed));
    }
    if(!limit)
        dp0[pos][pre][status]=res;
    return res;
}
ll dfs1(int pos,int pre,int status,int limit)
{
    if(pos<0)
        return status;
    if(!limit&&dp1[pos][pre][status]!=-1)
        return dp1[pos][pre][status];
    int ed=limit?digit[pos]:9;
    ll res=0;
    for(int i=pre;i<=ed;i++)
        res+=dfs1(pos-1,i,status||i,limit&&(i==ed));
    if(!limit)
        dp1[pos][pre][status]=res;
    return res;
}
void work(ll num)
{
    tot=0;
    while(num)
    {
        digit[tot++]=num%10;
        num/=10;
    }
}
ll solve(ll num)
{
    if(num==0)
        return 0;
    ll ans=(tot-1)*9+digit[tot-1];
    ll temp=0;
    int tt=0;
    while(tt<tot)
        temp=temp*10+digit[tot-1],tt++;
    if(temp>num)
        ans--;
    return ans;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T_T;
    scanf("%d",&T_T);
    ones(dp0);
    ones(dp1);
    for(int kase=1;kase<=T_T;kase++)
    {
        ll num;
        read(num);
        num--;
        work(num);
        ll temp0=dfs0(tot-1,10,0,1);
        temp0+=dfs1(tot-1,0,0,1);
        temp0-=solve(num);
        read(num);
        work(num);
        ll temp1=dfs0(tot-1,10,0,1);
        temp1+=dfs1(tot-1,0,0,1);
        temp1-=solve(num);
        ptlld(temp1-temp0);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/youmi/p/5935085.html

题目描述: 某公园有N(3≤N≤50)棵树排成一排,已知每棵树的高度。现要去掉一些树,使得剩下树的高度从左至右呈现先递增再递减的规律(即剩余的树中仅有一棵最高的树,且它左侧的所有树中后一棵树都要比前一棵树高,它右侧的所有树中后一棵树都要比前一棵树矮) 给出N棵树的高度(高度单位:m,1.0≤每棵树高度≤100.0,保留一位小数),请你计算出最少去掉几棵树才能使这排树呈现先递增再递减的规律,如果不能呈现则输出-1(只有递增或者只有递减都为不能呈现)。 例如:N=1010棵树的高度从左到右依次为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、付费专栏及课程。

余额充值