Luogu p3413(数位dp)

传送门

题意:

给你一个区间\([l,r]\),让你求着个区间中,存在长度至少为\(2\)的回文子串的数的个数。

分析:

因为所要求的区间相当的大,因此我们不妨从它的数位入手进行\(dp\)

根据萌数的性质,倘若要满足一个长度为\(len\)的字符串是萌数,那么这个串只需至少包含一个长度为\(2\)(对应偶回文),或者至少包含一个长度为\(3\)(对应奇回文)的回文串即可。至此,我们将题目进行了转化,即:如果一个串包含有一个长度为\(2\)或者长度为\(3\)的字符串,那么这个数就是萌数。

因此我们只需要讨论一个串的当前位\(pos\),它的前一位\(pre1\),以及它的前两位\(pre2\)即可。因此我们可以设计一个\(dp[pos][pre1][pre2]\)\(dp\)数组。但是我们发现,如果这样去设计状态的话,部分合法以及非法状态会发生重叠:如果当前的枚举到的状态是\(dp[1][2][3]\)的状态(即代表枚举到的子串的状态位\(123\))。此时的这个状态中,合法状态\(33123\)以及非法状态\(45123\)将会发生重叠。因此为了防止这种情况,我们考虑再开多一维判断是否在之前是否已经合法子串。故我们可以设计\(dp[pos][pre1][pre2][exist]\)代表,当前位置位\(pos\),前一位为\(pre1\),前两位为\(pre2\),且之前存在/不存在子串的方案数。

而我们发现,当设计了\(exist\)这个状态之后,我们就不需要记录前两位\(pre2\)的信息了,因此我们只需要开一个三维的\(dp\)数组\(dp[pos][pre1][exist]\)即可。

而把状态设计好之后,我们就只需要直接套用记忆化搜索的数位dp的板子就可以很简单的结局了。

代码:

#include <bits/stdc++.h>
#define maxn 1005
using namespace std;
typedef long long ll;
ll dp[maxn][15][2];
char L[maxn],R[maxn];
int bit[maxn],len;
const int mod=1e9+7;
ll dfs(int pos,int pre1,int pre2,int exist,int lead,int limit){
    if(pos==0) return exist;
    if(pre1!=-1&&pre2!=-1&&!lead&&!limit&&dp[pos][pre1][exist]!=-1)
        return dp[pos][pre1][exist];
    int up=limit?bit[pos]:9;
    ll res=0;
    for(int i=0;i<=up;i++){
        int p=(lead&&i==0)?-1:i;
        res+=dfs(pos-1,p,pre1,exist|(p!=-1 && (p==pre1||p==pre2)),p==-1,limit&&(p==up));
        res%=mod;
    }
    if(pre1!=-1&&pre2!=-1&&!lead&&!limit) dp[pos][pre1][exist]=res;
    return res;
}
ll solve(char *str,bool sub){
    len=strlen(str);
    for(int i=1;i<=len;i++)
        bit[len-i+1]=str[i-1]-'0';
    if(sub){
        int now=1;
        while(bit[now]==0) bit[now++]=9;
        bit[now]--;
        while(!bit[len] && len) len--;
    }
    memset(dp,-1,sizeof(dp));
    return dfs(len,-1,-1,0,1,1);
}
int main()
{
    scanf("%s%s",L,R);
    printf("%lld",((solve(R,0)-solve(L,1))%mod+mod)%mod);
    return 0;
}
posted @ 2019-08-02 11:18  ChenJr  阅读(128)  评论(0编辑  收藏  举报