学会了数位dp你能水的题就多了,我水了4绿7蓝2紫。
数位dp建议采用记忆化搜索形式(dp形式的我不会)。
我的先上题。
我们定义 dp_{i,j} 表示搜索到第 i 位上一位是 j 的满足条件的数字个数。
剩下的我们拿代码讲。
首先是简介的主函数
int main()
{
cin>>l>>r;
cout<<work(r)-work(l-1);//这里用到前缀和的思想,work(i)表示前i个数满足条件的个数
return 0;
}
然后是 work 函数
int work(int x)
{
memset(dp,-1,sizeof(dp));//初始全是-1
n=0;
while(x!=0)//拆位
{
v[++n]=x%10;
x/=10;
}
return dfs(n,-2,1,1);//下面我们会说到,第一个参数是哪一位(因为拆位是倒序的所以这里也要倒序),第二个参数是上一个数是多少(-2是为了满足第一位一定满足条件),第三个参数是是否全是0,第四个参数是有没有数字上限(比如数字114514,在第一位有数字上限,只能枚举到1,在11450x的时候,这个x就没有限制了可以是0~9)。
}
最重要的记忆化搜索来了
int dfs(int now,int last,bool zero,bool limit)
{
int s=0,p;
if(!now) return 1;//边界
if(!zero&&!limit&&dp[now][last]!=-1) return dp[now][last];//记忆化(要注意要满足不是全是0也没有数字上限)
p=(limit?v[now]:9);//如果有上限就只能遍历到v[now]
for(int i=0;i<=p;++i)
if(abs(i-last)>=2)//要满足题目中的条件,和上一位要相差2及以上
{
if(zero&&!i) s+=dfs(now-1,-2,1,limit&(i==p));//(还是全是0的情况)
else s+=dfs(now-1,i,0,limit&(i==p));//其他情况
//这里limit&(i==p)的意思是之前没有位数限制或这次遍历没遍历到顶的情况,都没有位数限制
}
if(!zero&&!limit) dp[now][last]=s;//也是记忆化的核心部分
return s;
}