题解 [ZJOI2010]数字计数

传送门<-洛谷版

电梯<-bzoj版

这份代码是新手友好版,也算是自用版,注释自认为写的很详细。

希望对要学数位dp的人有所帮助

这份题解是记忆化搜索版的数位DP,个人还是比较建议用这种方式写,比较像一种模板了

要说的一切都在代码中

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 /*
 6     这是一个前置 
 7     FIRST 
 8     数位dp,每一次寻找都不可以超过原本给的数的大小
 9     即621,找到第二位时,搜到1or2可以,但搜到3就要剪枝
10     在这份题解中用num[]代替
11     num[i]代表原数字中第i位中的数位num[i],就是当你在记忆化搜索时达到的边界条件 
12     SECOND 
13     关于前导零,当你找一个三位数时,系统认定后续所有都是三位数,即当你找
14     二位数会变成 0** ,即在你实际应该找到的数前有了一个零即前导零
15     但是前导零你是不需要的,故,你应该在记忆化搜索时舍去 
16 */ 
17 long long l,r,ans,sum;
18 /*
19     这是一个具体的分析
20     first,
21     我们可以用数位dp的方式找到1~(L-1)和1~R的每个字符的出现次数
22     相减既可以得到L~R间每个数码的出现次数
23     second,
24     要是每个数码都要以类似于离线的方法做的话,(自认为)空间复杂度就会boom
25     所以要一边搜一边输出 
26 */ 
27 long long f[16][2][16][2];
28 /*
29     第一维,代表我们dp到的数字的第几位
30     第二维,代表现在找到的数位上的数是否与num[]一样,小于则安全,继续
31     其中1代表不相等,0代表相等 
32     第三维,代表这个数出现了几次(最多一个数有15位,所以开16) 
33     第四维,代表前面是否有前导0
34     其中1代表有,0代表没有 
35     f[][][][]代表我们找到第i位,是否合法的数码的出现次数 
36 */ 
37 long long num[16];
38 //建议从主程序->函数chuli()->函数dfs()看 
39 long long dfs(int pos,bool same,int sum,bool zero,int d){
40     /*
41         pos代表你搜到第几位,same代表他是否到极限,sum代表d出现几次
42         zero代表他是否有前导零,d代表你要查那个数码
43     */ 
44     long long ret=0;
45     //ret代表 d 在整个区间里d的出现次数,通过记忆化求解 
46     if(pos==0) return sum;
47     //如果你搜到第零位,则代表你搜完了,此时由于f[0][][][]是没有值的,所以要返回sum 
48     if(f[pos][same][sum][zero]!=-1) return f[pos][same][sum][zero];
49     //如果你搜到的这个状态他不是初始化的-1,则代表你搜过了,就要返回这个状态代表的值 
50     //也是一个记忆化的过程 
51     for(register int i=0;i<=9;i++){
52         //从零开始,看数码的出现次数 
53         if(!same&&i>num[pos]) break;
54         //如果他本来就到极限,且你找的这个数也到极限,就要断开搜索 
55         ret+=dfs(pos-1,same||(i<num[pos]),sum+((!zero||i)&&(i==d)),zero&&(i==0),d);
56         //如果成立,则搜另一个状态
57         /*
58             搜前一位
59             same的转移变为——上一个状态是否数码与极限一致,你要再搜的数码是否与极限一致
60             num的转移变为——上一个状态是否为(前导零,这一位是不是0),以及
61             你要找的是否是你这次所需要的,如果都满足,则代表你咋次查询是对的 
62             zero的转移变为——之前是否是前导零以及这一次是否也为零 
63             d也就还是d 
64         */
65     }
66     f[pos][same][sum][zero]=ret;
67     //当你0~9都查完时,最终状态就是ret 
68     return ret;
69 }
70 long long chuli(long long x,int d){
71     //这个函数是用来拆分数位上的代码以及求原始数的长度的 
72     int len=0;
73     while(x) num[++len]=x%10,x/=10;
74     memset(f,-1,sizeof(f));
75     return dfs(len,0,0,1,d);
76     //在这里,我们选择从最高位开始搜索 
77 }
78 int main(){
79     scanf("%lld%lld",&l,&r);//读入 
80     for(register int i=0;i<=9;i++){
81         //依次寻找0~9的数码出现次数 
82         if(i<9) cout<<chuli(r,i)-chuli(l-1,i)<<" "; 
83         else cout<<chuli(r,i)-chuli(l-1,i);
84     }
85     return 0;// That's all.Thankyou for your attention.
86 }

如有不同意见和要提的建议,欢迎来留言,回复偶有时差,希望谅解

猜你喜欢

转载自www.cnblogs.com/fallen-down/p/10539829.html
今日推荐