开坑数位dp

【背景】

在10月3日的dp专练中,压轴的第6题是一道数位dp,于是各种懵逼。

为了填上这个留存已久的坑,蒟蒻chty只能开坑数位dp了。

【例题一】[HDU2089]不要62

题目大意:给你一个区间[l,r],求区间内不含4和62的数的个数。

分析:首先  ans[l,r]=ans[0,r]-ans[0,l-1],这样成功将问题转化为了求区间[0,x]的答案,然后减一下即可。

   然后可以预处理出一个f[][]数组,f[i][j]表示表示在i位数中以j开头的满足条件的数的个数,那么显然f[i][j]=sum{f[i-1][k]} (0<=k<=9&&j!=4&&!(j==6&&k==2))  

   然后用一个digit[]数组记录当前x从高位到低位的数字,如x=529,则digit[1]=5,digit[2]=2,digit[3]=9

   最后从高到低按位累加答案即可。(这点不明白的可以看我的代码,毕竟有些东西只能意会,无法言传)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<algorithm>
 6 #include<cmath>
 7 #include<ctime>
 8 using namespace std;
 9 int n,m,digit[10],f[10][10];
10 inline int read()
11 {
12     int x=0,f=1;  char ch=getchar();
13     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
14     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
15     return x*f;
16 }
17 void pre()
18 {
19     memset(f,0,sizeof(f));
20     f[0][0]=1;
21     for(int i=1;i<=9;i++)
22         for(int j=0;j<=9;j++)
23             for(int k=0;k<=9;k++)
24                 if(j!=4&&!(j==6&&k==2))
25                     f[i][j]+=f[i-1][k];
26 }
27 int ask(int x)
28 {
29     int len=0,ans=0;
30     while(x){digit[++len]=x%10;x/=10;}
31     digit[len+1]=0;
32     for(int i=len;i;i--)
33     {
34         for(int j=0;j<digit[i];j++)  if(j!=4&&!(j==2&&digit[i+1]==6))  ans+=f[i][j];
35         if(digit[i]==4||(digit[i]==2&&digit[i+1]==6))  break;
36     }
37     return ans;
38 }
39 int main()
40 {
41     //freopen("cin.in","r",stdin);
42     //freopen("cout.out","w",stdout);
43     pre();
44     while(1)
45     {
46         n=read();  m=read();
47         if(n==0&&m==0)  break;
48         printf("%d\n",ask(m+1)-ask(n));
49     }
50     return 0;
51 }
View Code

 【练习一】[bzoj1026]windy数

题目描述:windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,在A和B之间,包括A和B,总共有多少个windy数?

建议这题自己想做法,其实和上面那道题差不多了。

如果你真的wa到受不了,参考代码:http://www.cnblogs.com/chty/p/5981569.html

 【练习二】[hdu3555]bomb

题目大意:求给定区间的含有49的数的个数。

分析:我们可以转换一下思维,先求出区间内不含49的数的个数,然后用n减去这个数就行了,这就转化为了练习一的方法。(与网上其他题解不太一样,个人认为这种做法更简单)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 typedef long long ll;
10 ll T,digit[20],f[20][10];
11 inline ll read()
12 {
13     ll x=0,f=1;  char ch=getchar();
14     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
15     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
16     return x*f;
17 }
18 void pre()
19 {
20     f[0][0]=1;
21     for(ll i=1;i<=19;i++)
22         for(ll j=0;j<=9;j++)
23             for(ll k=0;k<=9;k++)
24                 if(!(j==4&&k==9))  f[i][j]+=f[i-1][k];
25 }
26 ll ask(ll x)
27 {
28     ll len=0,ans=0;
29     while(x) {digit[++len]=x%10; x/=10;}
30     digit[len+1]=0;
31     for(ll i=1;i<len;i++)
32         for(ll j=1;j<=9;j++)
33             ans+=f[i][j];
34     for(ll i=1;i<digit[len];i++)  ans+=f[len][i];    
35     for(ll i=len-1;i;i--)
36     {
37         for(ll j=0;j<digit[i];j++)  if(!(digit[i+1]==4&&j==9))  ans+=f[i][j];
38         if(digit[i+1]==4&&digit[i]==9)  break;
39     }
40     return ans;
41 }
42 int main()
43 {
44     //freopen("cin.in","r",stdin);
45     //freopen("cout.out","w",stdout);
46     T=read();  pre();
47     while(T--){ll n=read();printf("%I64d\n",n-ask(n+1));}
48     return 0;
49 }
View Code

 

 

 

【例题二】[poj3208]Apocalypse Someday

描述 Description
探险队员终于进入了金字塔。通过对古文字的解读, 他们发现,和《圣经》的作者想的一样,古代人认为 666 是属于魔鬼的数。不但如此,只要某数字的十进制表示中 有三个连续的 6,古代人也认为这个是魔鬼的数,比如 666,
1 666, 2 666, 3 666, 6 663, 16 666, 6 660 666 等等,统统是魔 鬼的数。
古代典籍经常用“第 X 大的魔鬼的数”来指代这些数。 这给研究人员带来了极大的不便。为了帮助他们,你需要写一个程序来求出这些魔鬼的数字。

题解详见http://blog.csdn.net/popoqqq/article/details/39319021

用上述方法写这道题的代码如下:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 typedef long long ll;
10 ll T,S,f[30][5],digit[30];
11 inline ll read()
12 {
13     ll x=0,f=1;  char ch=getchar();
14     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
15     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
16     return x*f;
17 }
18 void pre()
19 {
20     f[0][0]=1;
21     for(ll i=1;i<=29;i++)
22     {
23         f[i][0]=(f[i-1][0]+f[i-1][1]+f[i-1][2])*9;
24         f[i][1]=f[i-1][0];
25         f[i][2]=f[i-1][1];
26         f[i][3]=f[i-1][2]+f[i-1][3]*10;
27     }
28 }
29 int getans(ll x)
30 {
31     ll len=0,ans=0,cnt=0;
32     while(x)  {digit[++len]=x%10;  x/=10;}
33     for(ll i=len,j;i;i--)
34     {
35         ll sum;
36         for(int j=1;j<=digit[i];j++)
37         {
38             if(cnt==3)  sum=3;
39             else if(j==7)  sum=cnt+1;
40             else sum=0;
41             for(ll k=3;k>=3-sum;k--)  ans+=f[i-1][k];
42         }
43         if(cnt!=3)  cnt=(digit[i]==6?cnt+1:0);
44     }
45     return ans;
46 }
47 int main()
48 {
49     //freopen("cin.in","r",stdin);
50     //freopen("cout.out","w",stdout);
51     T=read();  pre();
52     while(T--) 
53     {
54         ll S=read(),l=0,r=100000000000ll;
55         while(l+1<r)
56         {
57             ll mid=(l+r)/2;
58             if(getans(mid+1)>=S)  r=mid;
59             else l=mid;
60         }
61         if(getans(r)==S)  printf("%I64d\n",l);
62         else printf("%I64d\n",r);
63     }
64     return 0;
65 }
View Code

 

posted @ 2016-10-20 14:07  chty  阅读(269)  评论(0编辑  收藏  举报