CQOI2016 手机号码 数位DP

CQOI2016 NKOJ3613 手机号码

问题描述

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

输入格式

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

输出格式

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

样例输入

12121284000 12121285550

样例输出

5

提示

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

数据范围:
        对于100%的测试数据,10^10≤L≤R<10^11。

按照题目中的限制自然地开出以下几维:

是否含有4
是否含有8
是否含有连续的3个数(以及配套的:前一个数,前一个数的前一个数)

为了搜索,开出以下两维:

当前讨论到第几位
之前是否都取到了最大

唯一需要稍作解释的就是“之前是否都取到了最大”。如果之前没有取到最大值,那么这一位最大就可以取9;反之则最大只能取当前讨论号码对应位上的数。举个例子:

讨论12345678910,如果第一位取1,讨论到第二位,那么第二位只能取0,1,2;如果第一位取0,那么第二位可以取0~9。

接下来就很容易写搜索了。为了不TLE,这里采用记忆化搜索。注意:如果之前没有都取到最大值,那么这个状态的数值才是任何时候都适用的。

#include
#include
#define ll long long
using namespace std;

ll cur[12],L[12],R[12],A,B,f[12][2][2][2][2][12][12];
ll DFS(ll x,ll m8,ll m4,ll m3,ll ml,ll l2,ll l1)
{
/*从左往右:当前讨论到第几位,是否含有8,是否含有4,是否出现了连续的三个数,之前是否都取到了最大,前一位的前一位,前一位*/
    if(m8&&m4)return 0;
    if(x==12)return m3;
    if(!ml&&(f[x][m8][m4][m3][ml][l2][l1]!=-1))return f[x][m8][m4][m3][ml][l2][l1];

    ll i,j,ans=0,a,b,c,lim;
    lim=ml?cur[x]:9;
    for(i=0;i<=lim;i++)
    {
        a=b=c=0;
        if(i==8)a=1;
        if(i==4)b=1;
        if(i==l2&&i==l1)c=1;
        ans+=DFS(x+1,m8|a,m4|b,m3|c,ml&&(i==lim),l1,i);
    }
    if(!ml)f[x][m8][m4][m3][ml][l2][l1]=ans;
    return ans;
}

int main()
{
    ll i,X,Y;

    scanf("%lld%lld",&X,&Y);
    X--;
    for(i=1;i<12;i++)L[12-i]=X%10,X/=10;
    for(i=1;i<12;i++)R[12-i]=Y%10,Y/=10;

    memset(f,-1,sizeof(f));
    for(i=1;i<12;i++)cur[i]=L[i];
    A=DFS(1,0,0,0,1,10,10);
    //一开始不存在前一位,所以给l1,l2都赋了一个不可能的值
    //注意一开始ml取1
    for(i=1;i<12;i++)cur[i]=R[i];
    B=DFS(1,0,0,0,1,10,10);
    printf("%lld",B-A);
}

你可能感兴趣的:(DP,省选)