Luogu P2602 [ZJOI2010]数字计数 //数位DP
数位DP第二题 原题链接
(话说我打题真的好少 状压好像也就才做了3道 就滚来学数位了= =)
看题一眼数位DP(这个真的只能看 看不出来也说不了什么)
数位DP老师教我们的是记忆化DFS写法 好写不爆栈 脑子不迷糊
特别注意处理前导0 , 数位DP好像难就难在处理前导零上吧 = =
爆搜的话想一想要往状态里塞什么东西
当我们搜完一个数,我们要知道什么时候搜完, 搜完之后这个数能对答案产生多少贡献 , 以及这个数是否合法 , 如何向后推导
老师已经帮我把这些讲好了......
long long dfs(int pos , int pre ,int goal, bool limit , bool lead)
pos 表示现在搜到了第几位, pre表示前面有几个要数的数(原题要求1-9)
goal 表示现在在数的数 , limit表示受限(这个后面说怎么用和怎么维护), lead表示前导零状态
1 pos 搜到第几位不用说, pos从原数长开始,到0结束
(边界)
2 pre 表示到这一位之前有几个目标数字(比如23333里面在处理第4位时,前面有2个3)
用于计算答案
3 goal 目标数字 原封不动往后传
用于辅助计算答案
4 limit 受限 在搜的过程中要注意不能超过原数字大小,那么limit肯定是一个连续的状态, 而且当这一位等于原数当前位的大小时,这个状态接着传递。
用来判断下一位最高可以取到哪
5 lead 前导零 如果这一位之前的数都是前导0 那么之前的0 都是不数的
用于计算0时 排除前导0的干扰
源代码在下面 结合理解食用
#include<bits/stdc++.h> using namespace std ; long long dp[30][20],cap[30],cnt,n,m; long long dfs(int pos , int pre ,int goal, bool limit , bool lead){ if(pos == 0) { return pre ; } if(!limit && !lead && dp[pos][pre] != -1) return dp[pos][pre]; int upp = (limit) ? cap[pos] : 9 ; long long ret = 0; for(int i=0; i <= upp; i++){ ret += dfs(pos-1, ((i==goal) && (!lead || i)) ? pre + 1 : pre, goal, (i==cap[pos] && limit), (i==0 && lead)); } if(!limit && !lead) dp[pos][pre] = ret; return ret; } long long solve (int dig,long long x){//注意这里的long long memset(cap,0,sizeof cap); memset(dp,0xff,sizeof dp); cnt=0; while (x){ cnt++; cap[cnt]=x%10; x/=10; } return dfs(cnt , 0 , dig ,true ,true); } int main(){ cin>>n>>m; for(int i=0;i<=9;i++){ cout<<solve(i,m)-solve(i,n-1)<<" "; } puts (""); return 0; }
TAG: SIN_XIII