题目链接:BZOJ 1026
我开始是用记忆化搜索写的,然后前导零处理挂了。然后改用数位DP的一般形式写的。写数位DP的一般过程可以总结为先预处理符合条件的没有限制的数,然后将小于限制的直接相加,然后再处理边界上的数(边界处理各种蛋疼)。数位DP的一个重要的思想就是逐位计算。
两篇比较好的国家集训队论文:浅谈数位统计类问题 数位计数问题解法研究
然后是我的代码:
#include<cstdio> #include<cstring> #include<iostream> #include<cmath> using namespace std; int base[20],f[20][20],dp[20][20]; inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void init(){ base[1]=1;//每一位的位权 for(int i=2;i<=10;i++)base[i]=base[i-1]*10; for(int i=0;i<=9;i++)f[1][i]=1;//预处理每一位没有限制时符合条件的数的个数 for(int i=2;i<=10;i++) for(int j=0;j<=9;j++) for(int k=0;k<=9;k++) if(abs((double)j-k)>=2)f[i][j]+=f[i-1][k]; } int find(int x){ if(!x)return 0; int t=0,w=10; while(base[w]>x)w--;//找到x是几位数 for(int i=1;i<w;i++)//小于该位的没有前导零的方案数直接相加 for(int j=1;j<=9;j++) t+=f[i][j]; ////////////////////////////////////////////////////////////// //考虑是w位数的情况 int limit=x/base[w];//limit为该位的限制 for(int i=1;i<limit;i++)t+=f[w][i]; x%=base[w]; int pre=limit; for(int i=w-1;i>=1;i--){ limit=x/base[i]; if(i!=1){ for(int j=0;j<limit;j++) if(abs((double)pre-j)>=2)t+=f[i][j]; } else{ for(int j=0;j<=limit;j++) if(abs((double)pre-j)>=2)t+=f[i][j]; } if(abs((double)pre-limit)<2)break;//不合限制直接退出循环防止多算 x%=base[i]; pre=limit; } return t; } int main(){ init(); int A=read(),B=read(); printf("%d\n",find(B)-find(A-1)); return 0; }
记忆化搜索写的先挖个坑之后再填吧。。。