不要62(数位dp普通写法和模板写法)

本文介绍了一种算法,用于计算指定区间内不含特定数字序列(如“62”或“4”)的整数数量。通过动态规划预先计算满足条件的数字组合,进而高效地解决给定问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多组数据,每次给定区间[n,m],求在n到m中没有“62“或“4“的数的个数。

如62315包含62,88914包含4,这两个数都是不合法的。0<n<=m<1000000

//https://blog.csdn.net/zhangxian___/article/details/75304335

假设我们现在把x分成了a1,a2,...,aL这样一个数组,长度为L,aL是最高位。

那么结果实际上就是这样:长度为L,最高位取[0,aL-1]的所有的符合题意数的和;再加上长度为L-1,最高位取aL,次高位取[0,aL-1-1]的所有符合题意数的和;再加上……;一直到第一位。

#include <iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=10;
long long f[maxn][10];

void getdp()
{
    f[0][0]=1;
    for (int i=1;i<10;i++)
    {
        for (int j=0;j<10;j++)
        {
            if (j==4) f[i][j]=0;
            else if (j==6)
            {
                for (int k=0;k<10;k++)
                    f[i][j]+=f[i-1][k];
                f[i][j]-=f[i-1][2];
            }
            else
            {
                for (int k=0;k<10;k++)
                    f[i][j]+=f[i-1][k];
            }
        }
    }
}

int a[maxn];
long long solve(int n)
{
    a[0]=0;
    while (n)
    {
        a[++a[0]]=n%10;//a1-an
        n/=10;//求出各个位上的数
    }
    a[a[0]+1]=0;//最高位补0
    long long ans=0;
    for (int i=a[0];i>=1;i--)//a[0]为位数,代表一共几位;
    {
        for (int j=0;j<a[i];j++)//j代表哪一位;
            if (j!=4 && !(a[i+1]==6 && j==2))
                ans+=f[i][j];//i位数,最高位为j
        if (a[i]==4) break;
        if (a[i+1]==6 && a[i]==2) break;
    }
    return ans;
}

int main()
{
    int n,m;
    getdp();
    while (scanf("%d %d",&n,&m)==2 && (n||m))
    {
        long long k1=solve(m+1);
        long long k2=solve(n);
        //printf("::%d,%d::",k1,k2);
        printf("%I64d\n",k1-k2);
    }
    return 0;
}

模板写法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
int a[20];
int dp[20][2];
int dfs(int pos,int pre,int sta,bool limit)
{
    if(pos==-1) return 1;
    if(!limit && dp[pos][sta]!=-1) return dp[pos][sta];
    int up=limit ? a[pos] : 9;
    int tmp=0;
    for(int i=0;i<=up;i++)
    {
        if(pre==6 && i==2)continue;
        if(i==4) continue;//都是保证枚举合法性
        tmp+=dfs(pos-1,i,i==6,limit && i==a[pos]);
    }
    if(!limit) dp[pos][sta]=tmp;
    return tmp;
}
int solve(int x)
{
    int pos=0;
    while(x)
    {
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,-1,0,true);
}
int main()
{
    int le,ri;
    //memset(dp,-1,sizeof dp);可优化
    while(~scanf("%d%d",&le,&ri) && le+ri)
    {
        memset(dp,-1,sizeof dp);
        printf("%d\n",solve(ri)-solve(le-1));
    }
    return 0;
}

 

<think>题目要求:计算两个数之间的所有整数的各位数字之 例如:输入a=10, b=12,则计算10、11、12的各位数字之。 10: 1+0 = 1 11: 1+1 = 2 12: 1+2 = 3 总 = 1+2+3 = 6 思路: 1. 输入两个整数ab(假设a<=b,否则交换) 2. 遍历从a到b的每一个整数 3. 对每一个整数,计算其各位数字之(使用循环取模整除10) 4. 累加每个整数的各位数字之 注意:负数如何处理?题目要求非负整数?但引用中都是非负整数,这里假设输入的是非负整数(或0)。如果输入负数,需要处理吗? 根据引用[1][2][3][4][5]都是非负整数,所以这里我们假设输入的两个整数都是非负整数,且a<=b(如果不是,则交换)。 步骤: 1. 输入两个整数ab 2. 如果a>b,则交换ab 3. 初始化总total_sum=0 4. 循环:从i=a到i=b 对于当前i,计算其各位数字之sum_i(注意:当i为0时,循环不会执行,所以需要单独处理0) 将sum_i加到total_sum 5. 输出total_sum 但是,如果ab的范围很大,比如从0到1000000,那么我们需要一个高效的方法吗?题目没有说明范围,我们先按照直接遍历的方法。 计算一个整数各位数字之的函数: int digit_sum(int n) { int s = 0; if (n == 0) return 0; // 或者单独处理0,但实际上下面的循环在n=0时不会执行,所以需要处理0 while (n) { s += n % 10; n /= 10; } return s; } 但是,注意:当n=0时,上面的循环不会执行,返回0。但0的各位数字之是0,所以正确。 但是,如果输入10,则digit_sum(10)=1+0=1,正确。 然而,如果范围很大,比如a=0, b=1000000,那么我们需要循环1000001次,每次计算digit_sum最多循环7次(因为1000000有7位),所以总操作次数在700万次左右,现代计算机可以接受。 但是,如果范围更大,比如十亿,那么digit_sum最多循环10次,但遍历的整数个数可能达到十亿,那么总操作次数100亿次,可能较慢(但在普通计算机上,C++大约需要几十秒?)。题目没有特别说明,我们按照普通情况处理。 优化:如果范围很大,我们可以考虑用数学方法加速,但题目没有要求,我们先给出普通解法。 代码步骤: 1. 输入两个整数ab 2. 如果a>b,交换 3. 初始化total_sum=0 4. for (int i = a; i <= b; i++) { total_sum += digit_sum(i); } 5. 输出total_sum 注意:当ab很大且相差很大时,这样可能效率低。但题目没有特别说明,我们先这样实现。 另外,题目要求是C++,所以使用C++编写。 但是,引用[3]中有一个计算各位数字之的代码,我们可以参考。 我们写一个digit_sum函数: 考虑0的情况:当n=0时,返回0。 另一种写法:可以处理0,因为0%10=0,然后0/10=0,所以如果循环条件改为do-while,则0会得到0。但这里我们用while循环,所以单独处理0?其实不用,因为n=0时,while(n)条件为假,直接返回0。 但是,负数呢?题目要求非负整数,所以不考虑负数。 代码: 注意:如果输入负数,我们按照非负整数的要求,可以提示错误或者取绝对值?但题目没有要求,我们假设输入非负整数。 如果输入了负数,我们可以将其视为非负整数处理(取绝对值)?或者按照题目要求,只处理非负整数,所以如果输入负数则报错。 根据问题描述,题目要求“两个数之间的整数”,如果输入负数,那么这些整数可能是负数,但负数的各位数字之定义?例如-10,各位数字之是1+0还是-1+0?一般我们取绝对值再求。但题目没有明确,所以我们按照非负整数处理,即要求输入的两个数都是非负整数。 如果用户输入负数,我们给出错误提示。 修改步骤: 1. 输入ab 2. 如果a<0或b<0,输出错误,并结束 3. 如果a>b,交换 但是,题目要求是“两个数之间的整数”,如果包含负数,那么这些整数是连续的,但负数的各位数字之如何定义?为了避免歧义,我们只处理非负整数。 因此,我们规定:输入的两个整数必须是非负整数(即>=0),否则报错。 代码实现: 例子:输入1012,输出6(因为10:1, 11:2, 12:3,总6) 我们按照这个思路写代码。 另外,引用[4]中给出了求整数位各位数字之的代码,我们可以参考其求各位数字之的部分。 下面给出完整代码:</think>### C++计算两个整数之间所有数的各位数字之 要计算两个整数之间所有数的各位数字之(包括这两个整数),可以按照以下步骤实现: #### 算法思路 1. **输入验证**:确保输入的两个整数都是非负整数 2. **范围处理**:如果第一个数大于第二个数,交换两者位置 3. **数字计算**: - 遍历区间内的每个整数 - 对每个整数计算其各位数字之 4. **结果累加**:将所有整数的各位数字之累加 #### 代码实现 ```cpp #include <iostream> #include <algorithm> // 用于swap函数 // 计算单个整数的各位数字之 int digit_sum(int num) { int sum = 0; while (num) { sum += num % 10; // 取最后一位数字 num /= 10; // 移除最后一位 } return sum; } int main() { int a, b; std::cout << "请输入两个非负整数(空格分隔): "; std::cin >> a >> b; // 验证输入 if (a < 0 || b < 0) { std::cout << "错误:输入必须是非负整数!" << std::endl; return 1; } // 确保a <= b if (a > b) { std::swap(a, b); } int total_sum = 0; // 遍历区间内的每个整数 for (int i = a; i <= b; i++) { total_sum += digit_sum(i); } std::cout << "从 " << a << " 到 " << b << " 的所有整数各位数字之为: " << total_sum << std::endl; return 0; } ``` #### 代码说明 1. **digit_sum函数**: - 使用取模运算`% 10`获取最后一位数字 - 通过整数除法`/ 10`移除最后一位 - 循环直到数字变为0 2. **主函数逻辑**: - 输入验证确保非负整数 - 使用`std::swap`确保a ≤ b - 遍历区间`[a, b]`,累加每个数的各位数字之 3. **时间复杂度**: - 对于区间内的每个数:$O(d)$,其中d是数字的位数 - 总时间复杂度:$O((b-a+1) \times \log_{10} n)$ #### 示例运行 ``` 请输入两个非负整数(空格分隔): 10 12 从 10 到 12 的所有整数各位数字之为: 6 ``` 计算过程: - 10 → 1 + 0 = 1 - 11 → 1 + 1 = 2 - 12 → 1 + 2 = 3 - 总 = 1 + 2 + 3 = 6 #### 优化建议 当区间范围很大时(如$10^6$以上),可以考虑以下优化: 1. **数学公式法**:使用数字的数学性质直接计算区间 2. **前缀优化**:预处理0~n的数字,区间 = sum[b] - sum[a-1] 3. **数位DP**:处理极大范围(如$10^{18}$)的高效算法
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值