Lucky 数

题目描述

定义一个数为 \(Lucky\) 数当且仅当以这个数中心为轴的对应数位的值不相等。

1231不是 \(Lucky\) 数因为第一位和最后一位对应相等,而1234是 \(Lucky\) 数。

求给定区间 \([A,B]\)\(Lucky\) 数的个数

解法

容易想到从 数位dp 这方面入手。

显然轴的前面部分和后面部分的操作是不一样的,所以想到把其分成两次不同的计数。而因为有前导零的存在,数的位数不确定,那么轴的位置就是不确定的,所以一共需要记录四种标记:\(st\) 当前枚举到第几位,\(lim\) 是否达到上界,\(lead\) 是否有前导零,\(len\) 除去前导零后数的长度。第一个计数只分两种转移,若 \(lead\) 为真并且当前位不是 \(0\),那么整个数的不为零部分就开始了,可以算出后面的长度 \(cnt-now+1\),并将前导零标记设为 \(0\);否则,则直接继承状态转移到下一位。当枚举的位置到了轴,分两种情况。若没有达到上界,则后面的每位都可以取 \(9\) 种值(除去轴前面对应的那个值),那么贡献就是 \(9\) 的后面数的位数次方;若达到上界,那么后面的数也要按上界处理,并保证对应位不相等即可,可以看出是一个简单的 \(dp\),随便处理一下即可。

#include<stdio.h>
#include<string.h>
#define LL long long

int s[20],cnt=0;
LL dp1[20][20],pw[20]={1},dp2[20][20];

LL dfs2(int now,bool lim,int len) {
    if(now==cnt+1) return 1;
    if(!lim&&~dp2[now][len]) return dp2[now][len];
    int rg=lim? s[now]:9; LL ret=0;
    for(int i=0;i<=rg;i++){
        int pos=cnt*2-len-now+1;
        if(s[pos]!=i)
            ret+=dfs2(now+1,lim&(i==rg),len);
    }
    if(!lim) dp2[now][len]=ret;
    return ret;
}

LL dfs1(int now,bool lim,bool lead,int len) {
    if(cnt-len/2+1==now){
        memset(dp2,-1,sizeof(dp2));
        if(!lim) return pw[len>>1];
        else return dfs2(now,lim,len);
    }
    if(!lim&&!lead&&~dp1[now][len]) return dp1[now][len];
    int rg=lim? s[now]:9; LL ret=0;
    for(int i=0;i<=rg;i++){
        if(lead&&i) ret+=dfs1(now+1,lim&(i==rg),0,cnt-now+1);
        else ret+=dfs1(now+1,lim&(i==rg),lead,len);
    }
    if(!lim&&!lead) dp1[now][len]=ret;
    return ret;
}

inline void swap(int &x,int &y){x^=y,y^=x,x^=y;}
LL solve(LL x) {
    memset(dp1,-1,sizeof(dp1));
    cnt=0;
    while(x) s[++cnt]=x%10,x/=10;
    for(int i=1;i<=(cnt>>1);i++) swap(s[i],s[cnt-i+1]);
    return dfs1(1,1,1,0);
}

LL A,B;
int main() {
    freopen("lucky.in","r",stdin);
    freopen("lucky.out","w",stdout);
    for(int i=1;i<=10;i++) pw[i]=pw[i-1]*9;
    scanf("%lld%lld",&A,&B);
    printf("%lld",solve(B)-solve(A-1));
}
posted @ 2020-10-05 21:46  Kreap  阅读(120)  评论(1编辑  收藏  举报