一般来说,数位DP可以解决类似的问题
-
求中符合某种特定要求的数的个数
按照一般DP的思想,我们会认为应该这么设:表示1~i中间符合要求的数的个数
但是通常来看,对于一般的数位DP这样并不能列方程,数位DP的要求一般和数位有莫大的关系,而从这个状态中我们可以发现这和数位并没有太大关系,因此不太好写方程。
那么我么就可以考虑一下其他的状态,下面我们可以结合一道题来看一下:
Windy数
题意:求在中不含前导零且相邻两个数的差至少为2的正整数的个数。
先来考虑暴力,显然有:
for(int i=l;i<=r;i++) if(check(i)) ans++;
但显然这种暴力一定会TLE,并且我们没有充分利用题目要求的优越性。
考虑记搜,我们可以设dp[i][j]表示搜到第i位,上一位数j为的方案总数。
然后考虑如何控制边界,比如:
2541654为最大值,我们当前搜到2541***时,显然下一位不能超过6。
所以我们应该将边界值拆开成一位一位的数,这样比较好算。
但是我们发现如果要处理最小值时,比较不好处理,因此我们可以利用前缀和的思想来求解,求出~与~的答案即可。
对于每一位枚举时,要注意我们要从~枚举,因此会有可能有前导零的影响。具体还有一些细节看代码:
#include<bits/stdc++.h> using namespace std; inline int read() { int f=1,w=0;char x=0; while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();} while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();} return w*f; } long long l,r,len; long long dp[21][21],a[21]; long long dfs(int pos,int las,int zer,int hig) { if(pos>len) return 1; if(!hig&&dp[pos][las]) return dp[pos][las];//注意这里的高位判定 long long ans=0; int maxn=hig?a[len-pos+1]:9;//能枚举到的最大值,与高位有关 for(int i=0;i<=maxn;i++) { if(abs(las-i)<2) continue; if(zer&&!i) ans+=dfs(pos+1,-2,1,0); else ans+=dfs(pos+1,i,0,hig&&i==maxn); } if(!hig&&!zer) dp[pos][las]=ans;//注意这里的高位判定 return ans; } inline long long Work(int x)//拆分边界 { len=0; while(x) a[++len]=x%10,x/=10; memset(dp,0,sizeof(dp)); return dfs(1,-2,1,1);//因为要从零枚举起,所以要令0位上为-2; } int main(){ l=read(),r=read(); printf("%lld",Work(r)-Work(l-1)); }
因为数位DP不太好,又做了一个题:
从这一题中,我们可以很显然的看出数位DP的套路性
当然,数位DP也要考虑细节,像高位判定这样的细节一定不能出错!
除此之外,这一题的二分也稍稍有些意思,可以好好思考一下。
在每次处理一个询问之后不必将清零,因为当前的对先一个询问没有影响,保留下来可以节省时间!
#include <iostream> #include <cstdio> #include <fstream> #include <algorithm> #include <cmath> #include <deque> #include <vector> #include <queue> #include <string> #include <cstring> #include <map> #include <stack> #include <set> using namespace std; #define int long long inline int read() { int f=1,w=0;char x=0; while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();} while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();} return w*f; } const int N=12; int f[N][N][N][2],x[N],cnt; inline int dfs(int pos,int las1,int las2,int jud,int hig) { if(!pos) return jud; if(!hig&&f[pos][las1][las2][jud]) return f[pos][las1][las2][jud]; int maxn=hig?x[pos]:9,ans=0,p=(las1==6&&las2==6); for(int i=0;i<=maxn;i++) ans+=dfs(pos-1,i,las1,jud||(p&&i==6),hig&&(i==maxn)); if(!hig) return f[pos][las1][las2][jud]=ans; else return ans; } inline int check(int s) { cnt=0;while(s) x[++cnt]=s%10,s/=10; return dfs(cnt,0,0,0,1); } main(){ int t=read(); while(t--) { int m=read(); int l=1,r=1e10; while(l<r) { int mid=l+r>>1; if(check(mid)>=m) r=mid; else l=mid+1; } printf("%lld ",r); } }