开坑数位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 }
【练习一】[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 }
【例题二】[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 }