手机号码(数位dp-dfs)

1. 问题描述:

人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数 量。
工具需要检测的号码特征有两个:号码中要出现至少3个相邻的相同数字,号码中不能同时出现8和4。号码必须同时包含两个特征才满足条件。满足条件的号码例如:13000988721、 23333333333、14444101000。而不满足条件的号码例如:1015400080、10010012022。 手机号码一定是11位数,前不含前导的0。工具接收两个数L和R,自动统计出[L,R]区间内所有满足条件的号码数量。L和R也是11位的手机号码。

输入描述:

输入文件内容只有一行,为空格分隔的2个正整数L,R。
10^10 ≤  L ≤  R < 10^11

输出描述:

输出文件内容只有一行,为1个整数,表示满足条件的手机号数量。

示例1
输入
12121284000 12121285550
输出
5
样例解释
满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550

链接:https://ac.nowcoder.com/acm/problem/19945
来源:牛客网

2. 思路分析:

这道题目与牛客中明七暗七的题目是类似的,都是数位dp的内容,只是这道题目在数中的位上多了几个限制条件:号码中至少需要3个连续相同的数字,不能同时出现4和8,首位不能够为0。主要还是使用递归来尝试每一位可能的数字,在递归的过程中记录下相关的限制条件对应的变量值。我们可以通过在递归方法中传递参数来记录这些限制条件。递归方法需要传递的参数有:当前递归的位置(从高位开始递归)pos,上一次出现的数字last,最开始出现的数字num,是否出现连续3个相同的布尔型变量ok,到目前位置是否出现4-has4,到当前的位置是否出现了8-has8,一个用来记录是否是最高位的最大数字的变量flag,用来控制下一个低位能够填的数字范围,是[0,9]还是[0,x](在数位dp的递归方法中这个变量感觉是必须的),用来标记当前的位置是否是最高位的first变量。可以声明一个六维的列表(列表的维度对应方法中动态变化的参数(python中创建高维列表:逆序创建),记忆型的递归都是类似的都是通过在递归过程中动态变化的参数声明对应维度的数组或者列表,每一维度都有其对应的含义这样可以减少子问题的重复求解,而且在数位dp使用递归解决的时候一般都需要这个记忆型列表来减少递归的时间复杂度。递归的主要思想是使用方法中的flag变量来得到当前位置可以填的数字范围,在递归的过程中更新对应参数的变化,使用六维列表来记录递归过程的结果,当发现当前位置的列表值为之前求解过那么直接返回列表对应的值即可。

3. 代码如下:

python代码:

nums = [0] * 13
# 六维列表(创建的时候注意括号的嵌套关系)
f = [[[[[[-1] * 2 for i in range(2)] for j in range(2)] for k in range(11)] for l in range(12)] for m in range(13)]


# num表示前一个数字, last表示最后一个数字(两个变量主要是计算连续的相同的数字的个数), ok记录是否出现过连续相同的三个数字, have4表示到当前位置是否出现过4,, hava8为是否出现过8, flag用来记录是否是最高位的最大数字, first用来标记是否是第一个位置也即最高位, 最高位不能够为0
def dfs(pos: int, last: int, num: int, ok: bool, have4: bool, have8: bool, flag: bool, first: int):
    if pos == 0:
        return 1 if ok and (not have4 or not have8) else 0
    if flag and f[pos][last][num][ok][have4][have8] != -1: return f[pos][last][num][ok][have4][have8]
    # 三目运算符
    x = 9 if flag else nums[pos]
    res = 0
    for i in range(0, x + 1):
        # 第一个数字不能够为0
        if i == 0 and first: continue
        res += dfs(pos - 1, i, last, ok or (i == last and i == num), have4 or i == 4, have8 or i == 8, flag or i < x, 0)
    if flag:
        f[pos][last][num][ok][have4][have8] = res
    return res


def cal(x: int):
    pos = 1
    while x:
        nums[pos] = x % 10
        x //= 10
        pos += 1
    return dfs(pos - 1, 0, 0, False, False, False, False, 1)


if __name__ == '__main__':
    l, r = map(int, input().split())
    # cal(r)计算[0:r]满足条件的数目, cal(l - 1)计算[0:l - 1]满足条件的数目
    print(cal(r) - cal(l - 1))

大佬的c++代码:

//记录最后出现的数字是几 上一位是几 是否已经有三连号 有没有出现8 有没有出现4

#include <bits/stdc++.h>
using namespace std;
#define int long long
int f[13][11][13][2][2][2];
int a[13];
int dp(int pos,int num,int last,bool ok,bool have8,bool have4,bool flag,bool first)
{
    if(pos==0) return ok&&((!have8)||(!have4));
    if(flag&&f[pos][num][last][ok][have8][have4]!=-1) return f[pos][num][last][ok][have8][have4];
    int x=flag?9:a[pos];
    int ans=0;
    for(int i=0;i<=x;i++)
    {
        if(first&&i==0) continue;
        ans+=dp(pos-1,i,num,ok||(i==num&&i==last),have8||i==8,have4||i==4,flag||i<x,0);
    }
    if(flag) f[pos][num][last][ok][have8][have4]=ans;
    return ans;
}
int cul(int x)
{
    if(x<1e10) return 0;
    int pos=0;
    while(x)
    {
        a[++pos]=x%10;
        x/=10;
    }
    return dp(pos,0,0,0,0,0,0,1);
}
signed main()
{
    memset(f,-1,sizeof(f));
    int l,r;
    scanf("%lld%lld",&l,&r);
    printf("%lld\n",cul(r)-cul(l-1));
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值