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;
}
原文地址:https://www.cnblogs.com/Chen-Jr/p/11287466.html