数位DP
数位 \(DP\) 一般用记搜解决,这是因为记搜便于理解,但是有时当问题相当简单时可以考虑其他实现方法。
考虑从最高位开始填数,在记忆化搜索时记录 \(pos\) 表示当前填到第几位,\(pre\) 表示上一个位置填的数是什么,\(lim\) 记录前面放的数是否顶上界,\(st\) 记录当前这位之前是否是前导零。
先把上界的每一位抠出来,那么当搜索放第 \(i\) 位时,要先确定这一位能放什么数,若前面都是贴着上界放的,那么这一位最多只能放 \(a_{pos}\),否则就不受限制。
然后在枚举第 \(i\) 位放什么时还要满足相邻数位数值之差大于等于 \(2\) 的限制,这个很好转移。
当然,若是前导零的话还要特别注意,因为这时 \(0∼9\) 都可以放,而如果没有考虑到这一点,最高位就只能至少放 \(2\) 了。
在加记忆化时还要注意,若出现了顶上界或前导零的情况是不能记忆化的.
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[50][50],num[50];
int a[50],len,ans(0);
int l,r;
int dfs(int pos,int pre,bool st,int lim){
if(pos>len) return 1;
if(pre>=0&&!lim&&~dp[pos][pre]) return dp[pos][pre];
int ret(0);
int res(lim?a[len-pos+1]:9);
for(int i=0;i<=res;++i){
if(abs(i-pre)<2) continue;
if(st&&!i) ret+=dfs(pos+1,-2,1,lim&&i==res);
else ret+=dfs(pos+1,i,0,lim&&i==res);
}
if(!lim&&!st) dp[pos][pre]=ret;
return ret;
}
int part(int x){
len=0;
for(;x;) a[++len]=x%10,x/=10;
memset(dp,-1,sizeof(dp));
return ans=dfs(1,-2,1,1);
}
signed main(){
scanf("%lld %lld",&l,&r);
printf("%lld",part(r)-part(l-1));
}

浙公网安备 33010602011771号