学习数位dp两天了,哈哈哈今天我这个菜鸡就来分享一下我的学习历程吧。
就以hdu2089作为模板来讲吧。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089
怎么讲呢,在完全没有接触数位dp的时候,第一眼看到的想法就是暴力(毕竟数据不大,其实就算是我会了数位dp看到这题我也会暴力做的,毕竟直接就可以过吗不是)
暴力写的是很早以前写的(不过要打表,不然会超时),我也把暴力写的代码放下面吧。
hdu2089暴力代码:
#include<string.h> #include<stdio.h> int d[1000010]; int c[10]; int e[1000010]; int main() { int a,b,ans=0; memset(e,0,sizeof(d)); for(int i=1;i<1000000;i++) { int flag=0; memset(c,0,sizeof(c)); c[1]=i/100000; c[2]=i%100000/10000; c[3]=i%10000/1000; c[4]=i%1000/100; c[5]=i%100/10; c[6]=i%10; for(int j=1;j<=6;j++) { if(c[j]==4) { flag=1; break; } if(c[j]==6&&c[j+1]==2) { flag=1; break; } } if(!flag) ans++; d[i]=ans; } while(scanf("%d%d",&a,&b)!=EOF) { if(a==0&&b==0)break; printf("%d ",d[b]-d[a-1]); } return 0; }
当然,暴力碰到一些大数就不行了。所以我们还是乖乖学会数位dp吧。
数位dp一般解决的问题是给你一个区间[a,b],然后给你一些限定条件,要你求在这样一个区间里,符合限定条件数有多少个。我们一般的思路是先求到0~b的符合条件的个数,然后再求0~a-1的符合条件的个数,然后再相减就是答案了。
下面我们先思考一个问题:给你一个区间[29,155],然后要你求数字中不含4的数字个数;
下面我先给出一个简单代码吧,讲起来会简单点:
样例简单代码:
#include<stdio.h> #include<string.h> int digit[15]; int dfs(int len,bool limit)//limit要好好理解一下,指的是限制条件 { if(!len)return 1;//!len表示搜索到了各位直接返回1,即所对应的ans+1 int ans=0;//因为每次都要搜完一个子循环才能到这,比如说搜123时,搜完十位0时,1时才能到这,然后把它们对应的ans的值都加到更高位的ans中 int maxn=(limit?digit[len]:9);//限制条件,不是末位的时候他的更低一位可以是0~9 for(int i=0;i<=maxn;i++) { if(i==4)//i==4直接跳过,不往下搜 continue; ans+=dfs(len-1,limit&&i==maxn);//limit&&i==maxn指的是满足前一位是末位当前也是末位 } return ans;//每一个循环结束返回ans的值 } int solve(int x)//求他的各个位上的数字 { int len=0; memset(digit,0,sizeof(digit)); while(x) { digit[++len]=x%10; x/=10; } return dfs(len,true); } int main() { int a,b; while(scanf("%d%d",&a,&b)!=EOF) { printf("%d ",solve(b)-solve(a-1)); } return 0; }
废话说的有点多,其实关键就是要注意没一个循环到哪里哪里结束,然后搞清楚每一个搜索到哪里结束,搞清楚每个ans的值返回到哪里,返回的是什么值。其实个人觉得,ans每次返回的是一个小循环的值,而且,对于一个更高位的循环来说,相对来说,每次返回的是一些平行的值,就像123来说,对于百位来说,十位的0和1就是一个小循环,然后十位0和十位1的ans平行的加入到百位的ans中(不知道能不能听懂,其实只要懂了就好多了)还有一个问题,像我们这么算是不是把00,之类的数也加进去了,其实这个问题也烦了我好一会。但是你有没有发现,我们算0~b和0~a-1的时候都算了00之类的啊,所以把他们相减,不就减掉了嘛,不影响最终答案啊!!!
为什么讲这是简单的代码呢,发现没有我们讲的是数位dp,上面代码中根本就没有dp!!!那么问题来了,上面写错了吗,没有!那么上面写的是什么呢,其实你仔细看就会发现我们每次都是要把每个数都遍历到个位才能返回1,所以这和直接暴力差不多的。所以现在你该知道,这里面的dp是干嘛的了,没错,就是来减少时间复杂度的。这里的dp又叫记忆化搜索,下面我给出加了dp的样例代码:
#include<stdio.h> #include<string.h> int digit[15]; int dp[20]; int dfs(int len,bool limit)//limit要好好理解一下,指的是限制条件 { if(!len)return 1;//!len表示搜索到了各位直接返回1,即所对应的ans+1 if(!limit&&dp[len]!=-1) return dp[len]; //当再次搜索时就直接用了不用再往下搜 ,即利用记忆的操作 int ans=0;//因为每次都要搜完一个子循环才能到这,比如说搜123时,搜完十位0时,1时才能到这,然后把它们对应的ans的值都加到更高位的ans中 int maxn=(limit?digit[len]:9);//限制条件,不是末位的时候他的更低一位可以是0~9 for(int i=0;i<=maxn;i++) { if(i==4)//i==4直接跳过,不往下搜 continue; ans+=dfs(len-1,limit&&i==maxn);//limit&&i==maxn指的是满足前一位是末位当前也是末位 } if(!limit)dp[len]=ans;//这是一个储存的操作,即记忆的操作 return ans;//每一个循环结束返回ans的值 } int solve(int x)//求他的各个位上的数字 { int len=0; memset(digit,0,sizeof(digit)); while(x) { digit[++len]=x%10; x/=10; } return dfs(len,true); } int main() { memset(dp,-1,sizeof(dp));//先初始化为-1 int a,b; while(scanf("%d%d",&a,&b)!=EOF) { printf("%d ",solve(b)-solve(a-1)); } return 0; }
应该看一下就能懂吧,记忆化搜索可以减少时间复杂度。上面讲的很清楚,这里不想再多说了,下面直接给出hdu2089的用数位dp做的代码,好好理解一下,基本就会了。
hdu2089:
#include<stdio.h> #include<string.h> #define ll long long int digst[20]; int dp[20][2]; ll dfs(int len,bool panduan_6,bool limit) { if(!len) return 1; if(!limit&&dp[len][panduan_6]!=-1) return dp[len][panduan_6]; ll ans=0; ll maxn=(limit?digst[len]:9); for(ll i=0;i<=maxn;i++) { if(i==4) continue; if(i==2&&panduan_6) continue; ans+=dfs(len-1,i==6,limit&&i==maxn); } if(!limit) dp[len][panduan_6]=ans; return ans; } ll solve(ll x) { int len=0; memset(digst,0,sizeof(0)); while(x) { digst[++len]=x%10; x/=10; } return dfs(len,false,true); } int main() { memset(dp,-1,sizeof(dp)); ll a,b; while(scanf("%lld%lld",&a,&b)!=EOF) { if(a==0&&b==0) break; printf("%lld ",solve(b)-solve(a-1)); } return 0; }
说实话,数位dp到目前为止,我就写了这一道题。以后还有很多题目要写,加油吧!少年!
你窥探过我最不安的手,在青春最寒冷的时候。