数位DP 顾名思义就是在数位上搞搞DP QAQ
之所以要引入数位的概念完全就是为了dp。数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。
DP 状态多半采用 dp[pos][..........] 来表示 ,其中POS 表示位数 ,......则是不同的题目的不同限制,dp[][]一般存放方案数
状态转移就按照题意与DP性质来枚举每一位的状态,然后将所有状态的总和都存到 pos 这一位中
然后 你就会想 好像这样搞不太对
同一题目的不同组数据,可能会产生不一样的限制,这时枚举到pos位时再直接调用dp[pos]会产生错误
怎么办QAQ
想着在记忆化搜索中把这些限制带上
这就产生了limit 和 lead这两个骚东西
其中limit 表示 你所能枚举的最大范围
比如你要枚举 0 到 216 中的数
如果你第一位枚举了 2,那么就达到了最大范围(limit=1)
那么你第二位只能选择 0 和 1
如果你第一位枚举的是 1(limit=0)
那么你下一位从0 至 9 都可以选择(19X肯定比2XX小嘛)
lead 表示第pos之前的二进制数位都是0(就是前导零)
有的题目条件下前导零会对DP产生影响
所以搜索的时候把这个状态转移一下
模板如下
inline int dfs(int pos,int XXX,bool lead,bool limit){ if (pos==-1)return XXX;//所有数位都已经枚举完毕 if (!lead&&!limit&&dp[pos][XXX]!=-1)return dp[pos][XXX];//记忆化 int ans=0; int up= limit? a[pos]:X; for (int i=0;i<=up;i++){//枚举当前数位所有可能性 if (lead&&i==0)ans+=dfs(pos-1,XXX,lead,limit & (i==a[pos]));//处理 前导零 else ans+=dfs(pos-1,dp转移,0,limit & (i==a[pos]));// 处理 limit ::limit & (i==a[pos]) } if (!limit&&!lead)dp[pos][sta]=ans;//记忆化 return ans; } inline int solve(int x){ int pos=0; while (x){ a[pos++]=x&1;//拆解每一位上的最大范围 x>>=1; } return dfs(pos-1,XXX,1,1);//这里 lead 和 limit 一定要是 1 QAQ 感性认知一下 }
有些注意事项(蒟蒻认为是一些技巧QAQ):
~~~~~从 a 到 b 枚举,可以处理为 0 到 b 减去 0 到 (a-1)
~~~~~memset(dp,-1,sizeof(dp))
~~~~~某些求和可以转换为算差
~~~~~dp[pos]里面的值一般为数的固有属性而非题目上的一些限制属性(这是即使性质也是做题时要考虑到的点)
就这样吧(应该吧)