[数位DP][记忆化搜索]JZOJ 3316 非回文数字

Description

如果一个字符串从后往前读与从前往后读一致,我们则称之为回文字符串。当一个数字不包含长度大于1的子回文数字时称为非回文数字。例如,16276是非回文数字,但17276不是,因为它包含回文数字727。

你的任务是在一个给定的范围内计算非回文数字的总数。
 

Input

输入仅一行,包含两个整数a和b。

Output

输出仅一行,包含一个整数,表示a到b范围内(包括a和b)非回文数字的总数。
 

Sample Input

输入1:
123 321
输入2:
123456789 987654321

Sample Output

输出1:
153
输出2:
167386971
 

Data Constraint

25%的数据:b-a<=100 000.

100%的数据:0<=a<=b<=10^18

分析

带限制求两数中总数,较显然是数位DP

我们考虑,如果第i位与其i-1位和i-2位都不相同,那么这个数是非回文子树

那么我们设f[i][j][k][l]表示做到第i位,i-2位为j,i-1位为k,是否在边界上

但是我们必须注意特殊的状况:前导零

那么我们设多两位状态[zero2][zero1]表示i-2位是否前导零,i-1位是否前导零,对于前导零不需要进行回文的判断

数位DP的递推好恶心啊

记忆化搜索即可

 

#include <iostream>
#include <cstdio>
#include <memory.h>
using namespace std;
typedef long long ll;
ll a,ans;
ll f[20][10][10][2][2][2];
int c[20],len;

ll Calc(int dep,int pre2,int pre1,bool zero2,bool zero1,bool limit) {
    if (f[dep][pre2][pre1][zero2][zero1][limit]>=0) return f[dep][pre2][pre1][zero2][zero1][limit];
    ll ans=0;
    if (dep>len) return 1;
    if (limit) {
        for (int i=0;i<=c[dep];i++)
            if ((zero2||i!=pre2)&&(zero1||i!=pre1))
                ans+=Calc(dep+1,pre1,i,zero1,zero1&&(i==0),i==c[dep]);
    }
    else {
        for (int i=0;i<=9;i++)
            if ((zero2||i!=pre2)&&(zero1||i!=pre1))
                ans+=Calc(dep+1,pre1,i,zero1,zero1&&(i==0),0);
    }
    return f[dep][pre2][pre1][zero2][zero1][limit]=ans;
}

ll Solve(ll x) {
    if (x<0) return 0;
    len=0;memset(f,-1,sizeof f);
    while (x) c[++len]=x%10,x/=10;
    for (int i=1;i<=len/2;i++) swap(c[i],c[len-i+1]);
    return Calc(1,0,0,1,1,1);
}

int main() {
    scanf("%lld",&a);a--;
    ans-=Solve(a);
    scanf("%lld",&a);
    ans+=Solve(a);
    printf("%lld",ans);
}
View Code

 

posted @ 2019-07-05 07:23  Vagari  阅读(212)  评论(0编辑  收藏  举报