首页 > 其他 > 详细

数位 dp 总结

时间:2019-08-06 22:55:44      阅读:84      评论:0      收藏:0      [点我收藏+]

T1:不要 62

题干:

  杭州人称那些傻乎乎粘嗒嗒的人为 $62$(音:$laoer$)。

  杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。

  不吉利的数字为所有含有 $62$ 或 $4$ 的号码。例如: $62315$、$73418$、$88914$ 都属于不吉利号码。但是,$69842$ 虽然含有 $6$ 和 $2$ ,但不是 $6$ $2$ 连号,所以不属于不吉利数字之列。

  你的任务是,对于每次给出的一个牌照区间号,推断出交管局今后又要实际上给多少辆新的士车上牌照了。

题解:

  这道题只考虑每一位的数,而不是整体大小,我们就可以想到数位 dp 。我们先将左右两端点十进制拆分一下,总答案就是 1~r 的答案减去 1~l-1 的答案就是 [l.r] 区间的解。

  这道题作者采取的是记忆化搜索的方法。(所谓记忆化搜索,就是每搜到一个情况记录一下这个情况的解,下次搜到时不再重新计算)

  有两个限制条件:

  1、‘4’:直接在枚举时判掉即可。

  2、‘62’:我们需要维护一下上一个数是什么以供判断,并且记录下这个‘与众不同’的情况——再开半维数组

Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #define $ 10
 4 #define int long long 
 5 #define inf 0x7fffffff
 6 using namespace std;
 7 int m,n,k,t,sta[$],dp[$][2],up;
 8 inline int dfs(int lct,int pre,bool sx,bool lmt){
 9     if(!lct) return 1;
10     if(!lmt&&dp[lct][sx]!=-1) return dp[lct][sx];
11     int top=(lmt)?sta[lct]:9, ans=0;
12     for(register int i=0;i<=top;++i){
13         if(i==4||(pre==6&&i==2)) continue;
14         ans+=dfs(lct-1,i,i==6,(lmt&&i==top));
15     }
16     if(!lmt) dp[lct][sx]=ans;
17     return ans;
18 }
19 inline int work(int x){
20     memset(dp,-1,sizeof(dp)); up=0;
21     for(;x;x/=10) sta[++up]=x%10;
22     return dfs(up,-1,0,1);
23 }
24 signed main(){
25     while(~scanf("%lld%lld",&n,&m)){
26         if(m+n==0) break;
27         printf("%lld\n",work(m)-work(n-1));
28     }
29 }
View Code

 

T2Windy 数

题干:

  $Windy$ 定义了一种 $Windy$ 数:不含前导零且相邻两个数字之差至少为 $2$ 的正整数被称为 $Windy$ 数。

  $Windy$ 想知道,在 $l$ 和 $r$ 之间,包括 $l$ 和 $r$ ,总共有多少个 $Windy$ 数?

题解:

 

Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #define $ 21
 4 #define inf 0x3f3f3f3f
 5 using namespace std;
 6 int m,n,k,t,dp[$][$],sta[$],up;
 7 inline int abs(int x){    return (x<0)?-x:x;    }
 8 inline int dfs(int lct,int pre,bool lmt){
 9     if(!lct) return 1;
10     if(!lmt&&pre>=0&&dp[lct][pre]!=-1) return dp[lct][pre];
11     int top=(lmt)?sta[lct]:9, ans=0;
12     for(register int i=0,tmp;i<=top;++i){
13         if(abs(i-pre)<2) continue;
14         tmp=(i==0&&pre==-inf)?pre:i;
15         ans+=dfs(lct-1,tmp,(lmt&&i==top));
16     }
17     if(!lmt&&pre>=0) dp[lct][pre]=ans;
18     return ans;
19 }
20 inline int work(int x){
21     memset(dp,-1,sizeof(dp)); up=0;
22     for(;x;x/=10) sta[++up]=x%10;
23     return dfs(up,-inf,1);
24 }
25 signed main(){
26     scanf("%d%d",&n,&m);
27     printf("%d\n",work(m)-work(n-1));
28 }
View Code

 

T3手机号码

题干:

  人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量。

  工具需要检测的号码特征有两个:号码中要出现至少 $3$ 个相邻的相同数字;号码中不能同时出现 $8$ 和 $4$ 。

  号码必须同时包含两个特征才满足条件。满足条件的号码例如:$13000988721$、$23333333333$、$14444101000$。

  而不满足条件的号码例如:$1015400080$、$10010012022$。

  手机号码一定是 $11$ 位数,前不含前导的 $0$ 。工具接收两个数 $l$ 和 $r$,自动统计出 $[ l , r ]$ 区间内所有满足条件的号码数量。 $l$ 和 $r$ 也是 $11$ 位的手机号码。

  10^10<=l<=r<10^11

题解:

  

Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #define int long long
 4 #define $ 12
 5 using namespace std;
 6 int l,r,sta[$],dp[$][$][$][2][2][2],up;
 7 inline int dfs(int lct,int pre,int ppre,bool cnnct,bool eigh,bool fur,bool lmt){
 8     if(eigh&&fur) return 0;
 9     if(!lct)      return cnnct;
10     if(!lmt&&dp[lct][pre][ppre][cnnct][eigh][fur]!=-1)
11         return dp[lct][pre][ppre][cnnct][eigh][fur];
12     int top=(lmt)?sta[lct]:9, ans=0;
13     for(register int i=0;i<=top;++i)
14         ans+=dfs(lct-1,i,pre,(cnnct||(i==pre&&i==ppre)),(eigh||i==8),(fur||i==4),(lmt&&(i==top)));
15     if(!lmt) dp[lct][pre][ppre][cnnct][eigh][fur]=ans;
16     return ans;
17 }
18 inline int work(int x,int ans=0){
19     if(x<10000000000) return ans;
20     memset(dp,-1,sizeof(dp)); up=0;
21     for(;x;x/=10) sta[++up]=x%10;
22     for(register int i=1;i<=sta[up];++i)
23         ans+=dfs(10,i,0,0,i==8,i==4,i==sta[up]);
24     return ans;
25 }
26 signed main(){
27     scanf("%lld%lld",&l,&r);
28     printf("%lld\n",work(r)-work(l-1));
29 }
View Code

 

T4:花神的数论题

题干:

   众所周知,花神多年来凭借无边的神力狂虐各大 $OJ$、$OI$、$CF$、$TC$ …… 当然也包括 $CH$ 啦。

  话说花神这天又来讲课了。课后照例有超级难的神题啦…… 我等蒟蒻又遭殃了。花神的题目是这样的:

  设 $sum(i)$ 表示 $i$ 的二进制表示中 $1$ 的个数。给出一个正整数 $N$ ,花神要问你 $sum(1)-sum(N)$ 的乘积。(答案模 $10000007$ )

题解:

   

Code:

技术分享图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #define int long long
 4 #define mod 10000007
 5 #define $ 52
 6 using namespace std;
 7 int n,sta[$],up,dp[$][$][$],sum=1,ans[$];
 8 inline int dfs(int lct,int now,int tot,bool lmt){
 9     if(lct==0) return tot==now;
10     if(!lmt&&dp[lct][now][tot]!=-1) return dp[lct][now][tot];
11     int top=(lmt)?sta[lct]:1, cnt=0;
12     for(register int i=0;i<=top;++i)
13         cnt+=dfs(lct-1,now+i,tot,(lmt&&(i==top)));
14     if(!lmt) dp[lct][now][tot]=cnt;
15     return cnt;
16 }
17 inline int qpow(int a,int x,int ans=1){
18     for(;x;x>>=1,a=a*a%mod) if(x&1) ans=ans*a%mod;
19     return ans;
20 }
21 signed main(){
22     scanf("%lld",&n);
23     for(;n;n>>=1) sta[++up]=n&1;
24     for(register int i=1;i<=50;++i)
25         memset(dp,-1,sizeof(dp)), ans[i]=dfs(up,0,i,1);
26     for(register int i=1;i<=50;++i)
27         sum=sum*qpow(i,ans[i])%mod;
28     printf("%lld\n",sum);
29 }
View Code

 

T5:

题干:

   Fish 是一条生活在海里的鱼,有一天他很无聊,就开始数数玩。 他数数玩的具体规则是:

  1、确定数数的进制B

  2、确定一个数数的区间[L, R]

  3、对于[L, R] 间的每一个数,把该数视为一个字符串,列出该字符串的每一个(连续的)子串对应的B进制数的值。

  4、对所有列出的数求和。

  现在Fish 数了一遍数,但是不确定自己的结果是否正确了。由于[L, R] 较大,他没有多余精力去验证是否正确,你能写一个程序来帮他验证吗?

  输入格式:

  第一行仅有一个数B,表示数数的进制。

  第二行有N +1 个数,第一个数为N,表示数L 在B 进制下的长度为N,接下里的N个数从高位到低位的表示数L 的具体每一位。

  第三行有M+ 1 个数,第一个数为M,表示数R 在B 进制下的长度为M,接下里的M个数从高位到低位的表示数R 的具体每一位。

  输出格式:输出仅一行,即按照Fish 数数规则的结果,结果用10 进制表示,由于该数可能很大,输出该数模上 20130427 的模数。

题解:

  

Code:

 

数位 dp 总结

原文:https://www.cnblogs.com/OI-zzyy/p/11312068.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!