这是我写的第三道数位dp,前一道是windy数,其实本题和windy数差不多,只是需要判断的多了一位,在此我写一写关于这两题的思路
首先粗略的想,麻烦的有前导0,然而不难发现,某满位的数可以有上一个少一位的满位数转移而来,这也是“数位”,“dp”的第一次体现
对于任意非满位的数,我们设置数组nomax[i][j][k]为有i位,最高位为j,次位为k的非回文数的个数,我们对j,k,ii从0~9枚举,从低位的数dp到高位的数,对每个符合要求的数都有
for(int i=2;i<1005;i++)
{
for(int j=0;j<10;j++)
{
for(int k=0;k<10;k++)
{
if(j==k) nomax[i][j][k]=-1;
else if(i==2) nomax[i][j][k]=1;
else
{
for(int ii=0;ii<10;ii++)
{
if(ii==j||ii==k) continue;
nomax[i][j][k]+=nomax[i-1][k][ii];
nomax[i][j][k]%=mod;
}
}
}
}
}
这样我们就解决了比界限位小的所有回文数了
接下来就要求最大位的了
由于存在边界,我们使用一个free标记是否触碰到顶端,对于触碰到顶的我们要另外处理,我们从高位往低位dp,又题目可知我们需要从第n-2位开始操作,所以先遍历前两位可能的情况来进行dfs,若是小于最大边界,我们给个ture的free标记,表示某位可以任意取值,对于free的位,我们从0~9dp找到符合的数,对于触碰到边界的数,我们不能大于边界上该位的数,并且考虑将free的状态下传
大体思路就是这样,在计算的过程中,其实有很多重复的计算可以剪枝,如某数abc我正在枚举a,这时我已经把free的bc求出来了,在a++后再次遍历时只需要调出我们之前求过的数即可,这样我们就考虑一个记录数组mem[w][i][j]表示第w位前一位为j前两位为i的数量,当在free状态下得到了某个mem的值,我们便把它记录下来,下次遇见时在次使用即可
最后就是答案如何输出的问题了,我们在l~r间的回文数个数为(r-l+1)-dp(r)-dp(l)-(l是否为回文数),细节就是l,r巨大,我们可以对l,r不断的%mod,最后相减,减去dp结果在+mod,最后%mod就是答案了。
代码
#include <bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
ll nomax[1005][10][10]={0};//不是最高为的满位数的非回文数的数量
ll mem[1005][10][10]={0};//剪枝
int str[1005]={0};//数字
ll dfs(int w,int i,int j,bool free)
{
if(w<1) return 1;
if(free&&mem[w][i][j]!=-1) return mem[w][i][j];
ll res=0;
int smax;
if(!free) smax=str[w];//是否触碰边界
else smax=9;
for(int k=0;k<=smax;k++)
{
if(i!=k&&j!=k) res+=dfs(w-1,j,k,free||k<smax),res%=mod;//有free的接下来都是free,触碰边界的如果k==smax则还是触碰边界
}
if(free) mem[w][i][j]=res;//free则记录
return res;
}
ll dp(string x)
{
ll n=x.size();
if(n==1)//0-9都不是回文数
{
ll s=x[0]-'0';
return s+1;
}
for(int i=n;i>=1;i--)//由于位数由高往低,记录时注意与位数相符
{
str[i]=x[n-i]-'0';
}
for(int i=0;i<1005;i++)
for(int j=0;j<10;j++)
for(int k=0;k<10;k++)
mem[i][j][k]=-1;
ll res=0;
int imax=x[0]-'0';
int jmax=x[1]-'0';
//i没到达边界的情况
for(int i=1;i<imax;i++)
{
for(int j=0;j<10;j++)
{
if(i!=j) res+=dfs(n-2,i,j,1),res%=mod;
}
}
//i到达边界的情况
for(int j=0;j<=jmax;j++)
{
if(imax!=j) res+=dfs(n-2,imax,j,j<jmax),res%=mod;//考虑free
}
res+=10;//单位的数
for(int i=2;i<n;i++)
for(int j=1;j<10;j++)//最高位不能是前导0
for(int k=0;k<10;k++)
{
if(j!=k) res+=nomax[i][j][k],res%=mod;
}
return res;
}
int main()
{
string l,r;
cin>>l>>r;
for(int i=2;i<1005;i++)
{
for(int j=0;j<10;j++)
{
for(int k=0;k<10;k++)
{
if(j==k) nomax[i][j][k]=-1;
else if(i==2) nomax[i][j][k]=1;
else
{
for(int ii=0;ii<10;ii++)
{
if(ii==j||ii==k) continue;
nomax[i][j][k]+=nomax[i-1][k][ii];
nomax[i][j][k]%=mod;
}
}
}
}
}
ll res=0;
res=dp(r)-dp(l);
bool sign=0;//判断l是否为回文
for(int i=0;i<l.size();i++)
{
if(i+1<l.size()&&l[i]==l[i+1]) sign=1;
if(i+2<l.size()&&l[i]==l[i+2]) sign=1;
}
if(!sign) res++;
res%=mod;
//求出%下的r-l
ll a=0;
ll s=1;
for(int i=l.size()-1;i>=0;i--)
{
for(int j=0;j<l[i]-'0';j++)
{
a=(a+s)%mod;
}
s=(s*10)%mod;
}
ll b=0;
s=1;
for(int i=r.size()-1;i>=0;i--)
{
for(int j=0;j<r[i]-'0';j++)
{
b=(b+s)%mod;
}
s=(s*10)%mod;
}
//
ll all=(b-a+1)%mod;
printf("%lld\n",((all-res)%mod+mod)%mod);
return 0;
}