bzoj4521 [Cqoi2016]手机号码(数位)

Description

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

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

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

Sample Input
12121284000 12121285550

Sample Output
5

样例解释
满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550

分析:
数位dp

还是这个问题:
mmm做的题目和我莫名相似,senior guard

先想状态:
无非是:
f[i][j][0/1][0/1] 第i位填的是j,到第i位有没有4,有没有8
这个是我第一次想出来的状态
但是我们不知道有没有三个相邻的相同数字,那简单,再加一维
f[i][j][k][0/1][0/1] 包括第i位,之前的k个数字都一样
但是这样只记录了最后的连续情况,这个串的连续情况我们还是不知道
那简单,我们再来一维
f[i][j][k][0/1][0/1][0/1]
第i位填的是j;包括第i位,之前的k个数字都一样;到第i位为止是否有三个连续相同;到第i位有没有4,有没有8

好像这样就很完美了
f[i+1][j][k][0/1][0/1]

等我写完了之后,发现我这种转移方法只能解决99999999999以内的
看来我们还需要加一维,表示现在的数字和边界是否吻合
所以状态就变成了这样:
f[i][j][k][0/1][0/1][0/1][0/1]
第i位填的是j;包括第i位,之前的k个数字都一样;到第i位为止是否有三个连续相同;到第i位有没有4,有没有8;是否卡上界
这样就可以转移了

tip

看网上有再加一维表示是否卡下界
我是分成了两部分,仔细一想复杂度是一样的
但是代码复杂度大幅减少
这里写图片描述

这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

int f[15][15][15][2][2][2][2];
char s1[20],s2[20];
int a[20],b[20];

//f[i][j][k][0/1][0/1][0/1][0/1]
//第i位填的是j;包括第i位,之前的k个数字都一样;
//到第i位为止是否有三个连续相同;到第i位有没有4,有没有8;是否卡上界
ll doit(int *a)
{
    int i,j,k,l,s,p,o,c,d;
    bool f1,f2;
    memset(f,0,sizeof(f));
    for (i=1;i<=a[1];i++) f[1][i][1][0][i==4 ? 1:0][i==8 ? 1:0][i==a[1] ? 1:0]=1;
    for (i=1;i<=10;i++)    //2-10
        for (j=0;j<=9;j++)
            for (k=1;k<=i;k++)
                for (l=0;l<=1;l++)
                    for (s=0;s<=1;s++)
                        for (p=0;p<=1;p++)
                            for (o=0;o<=1;o++)
                                if (f[i][j][k][l][s][p][o])
                                {
                                    int tt;
                                    if (o) tt=a[i+1]; //卡上界
                                    else tt=9;
                                    for (c=0;c<=tt;c++)
                                    {
                                        int x=1;
                                        if (c==j) x=k+1; 
                                        f[i+1][c][x][l||x>=3][s||c==4][p||c==8][o&c==a[i+1]]+=f[i][j][k][l][s][p][o];
                                    }
                                }
    ll ans=0;
    for (i=0;i<=9;i++)
        for (j=1;j<=11;j++)
            for (k=0;k<=1;k++)
                ans+=(ll)f[11][i][j][1][0][1][k]+f[11][i][j][1][1][0][k]+f[11][i][j][1][0][0][k];
    return ans;
}

int main()
{
    scanf("%s%s",&s1,&s2);
    for (int i=0;i<11;i++)
    {
        a[i+1]=s1[i]-'0';
        b[i+1]=s2[i]-'0';
    }
    int j=11;
    while (!a[j]&&j>1)
    {
        a[j]=9;
        j--;
    }
    if (j==1&&(a[j]==0||a[j]==1))
        printf("%lld",doit(b));
    else
    {
        a[j]--;
        printf("%lld",doit(b)-doit(a));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值