洛谷P3413 SAC#1 - 萌数(数位DP)

题目

https://www.luogu.com.cn/problem/P3413

思路

不要被“回文”这个东西吓到了。考虑一个任意长度\(\geq 2\)的回文串,它必然包含一个长度为3或长度为2的回文子串。也就是说,只要串中存在\(S_{i}\)满足\(S_{i}==S_{i-1}\)\(S_{i}==S_{i-2}\),那么这个数就是萌数.

接下来就是数位DP的常规套路了,设\(f(x)\)\(1\)\(x\)中萌数的个数,最终答案就是\(f(r)-f(l-1)\)。考虑到本题\(l\)\(r\)只能以字符串形式存储,为了避免高精度减法,我们把答案换成\(f(r)-f(l)\),如果\(l\)自身是萌数,则答案加一。(这个直接暴力检验)。

最重要的就是数位DP的主过程了。

从高位向低位处理。

ll dfs(char *x,int digit,int limit,int k/*该数位前有多少个非前导零的位*/)

我的dfs过程是这么写的,其中\(x\)是字符数组,表示传进去的\(l\)\(r\),digit表示当前处理的是第几位(为了方便计算,这里指权值为\(10^{digit}\)的那一位),\(limit\)表示之前取的数是否是上限(这一点可能说的不是很清楚,可以看其他大佬的数位DP博客,\(limit\)基本都有相同的含义),\(k\)表示我们当前构造的萌数已经有了多少位(其实就是用来特判开头两个数字的,因为他们的\(digit-2\)不存在)。

设当前位为\(S_{digit}\),那么我们考虑它能否与\(S_{digit-1}\)\(S_{digit-2}\)相同,针对\(limit\)\(k\)的不同取值做不同的决策(这部分详见注释)。

分类讨论写的比较丑,大佬轻喷QWQ。

代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define mod (int)(1e9+7)
#define ll long long
#define maxn 1010
using namespace std;
char l[maxn],r[maxn];
ll pow_10[maxn],suf[maxn];//预处理10的次幂及l和r的后缀
ll dp[maxn][10][10];
int LEN;
ll dfs(char *x,int digit,int limit,int k/*该数位前有多少个非前导零的位*/){
    ll sum=0;
    if(digit<0) return 0;
    if(k==0){
        if(limit){//有限制
            sum+=(x[digit]-'0'-1)*dfs(x,digit-1,0,1)%mod;sum%=mod;//当前位不取0也不取到x[i],之后的选择不受限
            sum+=dfs(x,digit-1,1,1);sum%=mod;//当前位取到x[i],这将使之后的选择仍然受限
            sum+=dfs(x,digit-1,0,0);sum%=mod;//当前位空出,继续填0,之后的选择不受限
        }
        else{//无限制
            sum+=9*dfs(x,digit-1,0,1)%mod;sum%=mod;//当前填1~9,由于之前的填法已经使构造的值必然小于x,所以之后当然也没有限制
            sum+=dfs(x,digit-1,0,0);sum%=mod;//当前填0
        }
    }
    else if(k==1){
        if(limit){//有限制
            if(x[digit]>x[digit+1]){//x的当前位大于前一位,这使我们可以令这一位=x[digit+1],从而满足萌数条件
                sum+=(x[digit]-'0'-1)*dfs(x,digit-1,0,2)%mod;sum%=mod;//平凡值,不满足萌数也不是上限,取法有(x[digit]-'0'-1)种
                sum+=dfs(x,digit-1,1,2);sum%=mod;//取上限,之后受限
                sum+=pow_10[digit];sum%=mod;//取x[digit+1],使满足萌数条件,后面随便取,总计pow_10[digit](后面digit个位置每个有10种取法)
            }
            else if(x[digit]==x[digit+1]){//取上限时正好满足萌数条件,与大于的情况有一些微妙不同
                sum+=(x[digit]-'0')*dfs(x,digit-1,0,2)%mod;sum%=mod;
                sum+=suf[digit]+1;sum%=mod;//这里,虽然已经满足萌数条件,但此时若任意取有可能导致超出上界,所以不是10的次幂而是x的后缀
            }
            else{//该位已经无法满足萌数条件了
                sum+=(x[digit]-'0')*dfs(x,digit-1,0,2)%mod;sum%=mod;
                sum+=dfs(x,digit-1,1,2);sum%=mod;
            }
        }
        else{//无限制
            sum+=9*dfs(x,digit-1,0,2)%mod;sum%=mod;//取不等于前一位的值,有⑨种取法
            sum+=pow_10[digit];sum%=mod;//取等于前一位的数字,满足萌数条件
        }
    }
    else{//k>=2,与k==1相比无非多了一个与digit-2位的比较,只不过计数麻烦一点,注意细节
        if(limit){
            int c=0;
            if(x[digit+1]<x[digit]){
                c++;
                sum+=pow_10[digit];sum%=mod;
            }
            if(x[digit+1]==x[digit]){
                sum+=suf[digit]+1;sum%=mod;
            }
            if(x[digit+2]<x[digit]){
                c++;
                sum+=pow_10[digit];sum%=mod;
            }
            if(x[digit+2]==x[digit]){
                sum+=suf[digit]+1;sum%=mod;
            }
            sum+=(x[digit]-'0'-c)*dfs(x,digit-1,0,k+1)%mod;sum%=mod;
            if(x[digit]!=x[digit+1]&&x[digit]!=x[digit+2]){
                sum+=dfs(x,digit-1,1,k+1);sum%=mod;
            }
        }
        else{
            sum+=8*dfs(x,digit-1,0,k+1)%mod;sum%=mod;//与之前选择填上digit+1位,digit+2位均不同
            sum+=2*pow_10[digit]%mod;sum%=mod;//与其中之一相同
            //假设我们正在构造的萌数候选用y数组表示,值得注意的是y[digit+1]和y[digit+2]保证不同,否则我们早就因为满足萌数条件而直接计算出值了,不会走到这一步。
            //所以这里的8和2是确定的
        }
    }
    return sum;
}
ll f(char *s){
    LEN=strlen(s);
    for(int i=1;i<LEN;i++){
        suf[i]=suf[i-1]+(s[i-1]-'0')*pow_10[i-1];
    }//简简单单的预处理
    return dfs(s,LEN-1,1,0);
}
void rev(char *s){
    int n=strlen(s);
    for(int i=0;2*i<n-1;++i) swap(s[i],s[n-1-i]);
}
int main(){
    int i,flag=0,n;
    ll ans;
    scanf("%s%s",l,r);
    rev(l);rev(r);
    pow_10[0]=1;
    for(i=1;i<=1000;i++)
        pow_10[i]=pow_10[i-1]*10%mod;
    ans=f(r)-f(l);
    n=strlen(l);
    if(l[0]==l[1]) flag=1;
    for(i=2;i<n;i++){
        if(l[i]==l[i-1]||l[i]==l[i-2]){
            flag=1;
            break;
        }
    }
    if(flag) ans++;
    ans=(ans+mod)%mod;
    // printf("%lld %lld\n",f(l),f(r));
    printf("%lld",ans);
    // system("pause");
    return 0;
}
posted @ 2021-10-13 22:35  文艺平衡树  阅读(57)  评论(0编辑  收藏  举报