几道数位DP
因为这几天写的几道数位DP大多都太水。。而且也确实没什么好讲所以就扔到一起了。
[hdu4772]Good Numbers
要求统计区间内 各位数之和能被10整除 的数的个数。
练手,f[i][j][k]表示i位的数,以j开头,各位数之和取模10的结果为k,的方案数。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 int i,j,j1,k,k1,n,m; 7 ll f[19][10][10],ten[19],l,r; 8 9 int s[20],len; 10 inline ll get(ll x){ 11 if(x<0)return 0; 12 int i,j,k,pre;ll tmp,ans=0; 13 for(i=0;i<=18;i++)if(x==ten[i]){x--;break;} 14 if(!x)return 1; 15 for(tmp=x,len=0;tmp;tmp/=10)s[++len]=tmp%10; 16 17 for(i=1;i<len;i++)for(j=1;j<=9;j++)ans+=f[i][j][0]; 18 for(i=1;i<s[len];i++)ans+=f[len][i][0]; 19 20 for(pre=s[len],i=len-1;i;i--){ 21 k=(-pre+10)%10; 22 for(j=0;j<s[i];j++)ans+=f[i][j][k]; 23 pre=(pre+s[i])%10; 24 } 25 return ans+(pre==0)+1; 26 } 27 int main(){ 28 for(i=ten[0]=1;i<=18;i++)ten[i]=ten[i-1]*10; 29 for(i=0;i<=9;i++)f[1][i][i]=1; 30 for(i=2;i<=18;i++) 31 for(j=0;j<=9;j++)for(k=0;k<=9;k++) 32 for(j1=0,k1=(k-j+10)%10;j1<=9;j1++)f[i][j][k]+=f[i-1][j1][k1]; 33 34 for(scanf("%d",&m),n=1;n<=m;n++){ 35 scanf("%lld%lld",&l,&r); 36 printf("Case #%d: %lld\n",n,get(r)-get(l-1)); 37 } 38 return 0; 39 }
[hdu2089]不要62
要求统计区间内有多少个数 不包含62并且不包括4.
f[i][j]表示i位的数,以j开头,非法数字的个数。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 using namespace std; 5 int f[10][10],ten[10],sum; 6 int i,j,k,n,m; 7 int now[12]; 8 inline int get(int x){ 9 if(!x)return 0; 10 int i,len=0,ans=0; 11 for(int tmp=x;tmp;tmp/=10)now[++len]=tmp%10; 12 now[len+1]=0; 13 for(i=len;i;i--){ 14 for(j=0;j<now[i];j++){ 15 ans+=f[i][j];//,printf("add: %d %d\n",i,j); 16 if(j==2&&now[i+1]==6)ans+=ten[i-1]-f[i][j]; 17 } 18 if(now[i]==4||(now[i]==2&&now[i+1]==6)){ans+=x%ten[i-1]+1;break;} 19 } 20 // printf("%d ans:%d\n",x,ans); 21 return ans; 22 } 23 int main(){ 24 for(i=0;i<=9;i++)f[1][i]=i==4; 25 ten[0]=1,ten[1]=10; 26 for(i=2;i<=9;i++){ 27 ten[i]=ten[i-1]*10; 28 for(j=sum=0;j<=9;j++)sum+=f[i-1][j]; 29 for(j=0;j<=9;/*printf("%d %d %d\n",i,j,f[i][j]),*/j++) 30 if(j==4)f[i][j]=ten[i-1]; 31 else if(j==6)f[i][j]=sum+ten[i-2]-f[i-1][2]; 32 else f[i][j]=sum; 33 } 34 while(scanf("%d",&n)==1){ 35 scanf("%d",&m); 36 if(!n&&!m)break; 37 printf("%d\n",m-n+1-(get(m)-get(n-1))); 38 } 39 return 0; 40 }
[hdu3555]bomb
要求统计区间内有多少个包含49的数。
f[i][j]表示i位的数,j开头,包含49的数的个数。(因为49这个数比较特殊一点(9是最大的数)...统计的时候可以不写关于第二位数的特判)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 ll f[20][10],ten[23]; 7 int i,j,len,s[23]; 8 ll tmp,n; 9 inline ll get(ll x){ 10 ll ans=0; 11 for(len=0,tmp=x;tmp;tmp/=10)s[++len]=tmp%10;s[len+1]=0; 12 for(i=1;i<len;i++)for(j=1;j<=9;j++)ans+=f[i][j]; 13 for(i=len;i;i--){ 14 for(j=i==len;j<s[i];j++)ans+=f[i][j]; 15 if(s[i]==9&&s[i+1]==4){ans+=x%ten[i-1]+1;break;} 16 } 17 return ans; 18 } 19 int main(){ 20 for(i=ten[0]=1;i<=18;i++)ten[i]=ten[i-1]*(ll)10; 21 for(i=1;i<=19;i++) 22 for(j=0;j<=9;/*printf("%d %d %lld\n",i,j,f[i][j]),*/j++) 23 if(j!=4&&i>1)f[i][j]=f[i-1][j]*9+f[i-1][4]; 24 else f[i][j]=f[i-1][1]*8+f[i-1][4]+ten[i-2]; 25 int T; 26 for(scanf("%d",&T);T;T--) 27 scanf("%lld",&n),printf("%lld\n",get(n)); 28 return 0; 29 }
[hdu4507]恨7不成妻
预处理的时候,数字的平方和可以通过 方案数、数字和、数字的平方和 三个玩意算出来。。算是常见姿势了吧。
再具体点见代码注释
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int modd=1000000007; 7 struct zs{ 8 ll num,sum,sqr;//方案数,方案的数字和,平方和 9 }f[21][10][2][7][7];//f[i][x][j][k][l]:i位的数,开头数字为x,j表示有无某一位为7,k表示每一位加起来的和%7的值,l表示数字%7的值。 10 ll ten[23]; 11 ll l,r; 12 13 14 inline void add(zs &a,zs b,ll num){ 15 num%=modd; 16 a.num+=b.num,a.sum+=(b.sum+num*b.num)%modd, 17 a.sqr+=b.sqr+(b.sum*num%modd*2)+(b.num*num%modd*num%modd), 18 a.num-=a.num>=modd?modd:0,a.sum-=a.sum>=modd?modd:0, 19 a.sqr%=modd; 20 } 21 22 int s[20],len; 23 inline ll get(ll num){ 24 if(num==1)return 0; 25 register int i,j,k,x,pre2;ll tmp,ans=0,pre,pre1;zs b; 26 27 for(tmp=num,len=0;tmp;tmp/=10)s[++len]=tmp%10; 28 for(i=1;i<len;i++)for(x=1;x<=9;x++)for(j=1;j<7;j++)for(k=1;k<7;k++)ans+=f[i][x][0][j][k].sqr,ans-=ans>=modd?modd:0; 29 for(x=1;x<s[len];x++)for(j=1;j<7;j++)for(k=1;k<7;k++)ans+=f[len][x][0][j][k].sqr,ans-=ans>=modd?modd:0; 30 31 pre=s[len]*ten[len-1]%modd,pre1=s[len]*ten[len-1]%7,pre2=s[len]%7; 32 if(s[len]!=7) 33 for(i=len-1;i;i--){ 34 // printf(" i:%d pre1:%lld pre2:%d pre:%lld\n",i,pre1,pre2,pre); 35 for(x=0;x<s[i];x++)for(j=1;j<7;j++)for(k=1;k<7;k++) 36 b=f[i][x][0][(j-pre2+7)%7][(k-pre1+7)%7], 37 ans=(ans+b.sqr+b.sum*pre%modd*2+b.num*pre%modd*pre%modd)%modd; 38 pre=(pre+s[i]*ten[i-1])%modd;pre1=(pre1+s[i]*ten[i-1])%7;pre2=(pre2+s[i])%7; 39 if(s[i]==7)break; 40 } 41 return ans; 42 } 43 int main(){ 44 register int i,j,k,j1,k1,x;ll num; 45 46 for(i=ten[0]=1;i<=18;i++)ten[i]=ten[i-1]*10; 47 for(i=0;i<=9;i++)k=i%7,f[1][i][i==7][k][k]=(zs){1,i,i*i}; 48 for(i=2;i<=19;i++) 49 for(j=0;j<7;j++)for(k=0;k<7;k++) 50 for(x=num=0;x<=9;x++,num+=ten[i-1]){ 51 j1=j-x%7,j1+=j1<0?7:0,k1=k-(num%7),k1+=k1<0?7:0; 52 if(x!=7) 53 for(int x1=0;x1<=9;x1++)add(f[i][x][0][j][k],f[i-1][x1][0][j1][k1],num); 54 else 55 for(int x1=0;x1<=9;x1++)add(f[i][x][1][j][k],f[i-1][x1][0][j1][k1],num); 56 for(int x1=0;x1<=9;x1++)add(f[i][x][1][j][k],f[i-1][x1][1][j1][k1],num); 57 } 58 59 int T; 60 for(scanf("%d",&T);T;T--){ 61 scanf("%lld%lld",&l,&r); 62 printf("%lld\n",(get(r+1)-get(l)+modd)%modd); 63 } 64 return 0; 65 }
[hdu3652]B-number
要求出区间内包含13,并且能被13整除的数的个数。
f[i][j][k][l]表示i位数,开头为j,k表示是否包含13,l表示数字模13后的结果,的方案数
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 int ten[11]; 7 int f[11][10][2][13]; 8 int i,j,k,j1,k1,n,m; 9 10 int s[12],len; 11 inline int get(int x){ 12 if(x<13)return 0; 13 register int i,j,k,j1,k1,ans=0,pre;int tmp; 14 for(len=0,tmp=x;tmp;tmp/=10)s[++len]=tmp%10; 15 for(i=1;i<len;i++)for(j=1;j<=9;j++)ans+=f[i][j][1][0]; 16 for(i=1;i<s[len];i++)ans+=f[len][i][1][0]; 17 18 pre=(s[len]*ten[len-1])%13;bool flag=0; 19 for(i=len-1;i;i--){ 20 k=(-pre+13)%13; 21 if(!flag) 22 for(j=0;j<s[i];j++)ans+=f[i][j][1][k]; 23 else 24 for(j=0;j<s[i];j++)ans+=f[i][j][1][k]+f[i][j][0][k]; 25 if(!flag&&s[i+1]==1&&s[i]>3)ans+=f[i][3][0][k]; 26 if(s[i]==3&&s[i+1]==1)flag=1; 27 pre=(pre+s[i]*ten[i-1])%13; 28 } 29 if(flag)ans+=pre==0; 30 return ans; 31 } 32 int main(){ 33 for(i=ten[0]=1;i<=10;i++)ten[i]=ten[i-1]*10; 34 for(i=0;i<=9;i++)f[1][i][0][i]=1; 35 for(i=2;i<=10;i++)for(j=0;j<=9;j++){ 36 37 for(k=0;k<13;k++){ 38 for(j1=0,k1=(k-j*ten[i-1]%13+13)%13;j1<=9;j1++){ 39 if(j!=1||j1!=3) f[i][j][0][k]+=f[i-1][j1][0][k1]; 40 else f[i][j][1][k]+=f[i-1][j1][0][k1]; 41 f[i][j][1][k]+=f[i-1][j1][1][k1]; 42 } 43 // printf(" %d %d %d %d\n",i,j,k,f[i][j][1][k]); 44 } 45 } 46 while(scanf("%d",&n)==1) 47 printf("%d\n",get(n)); 48 49 return 0; 50 }
[hdu3709]Balanced Number
无力去膜题解。http://blog.csdn.net/acm_cxlove/article/details/7821726
顺便感受了一下记忆化搜索在处理边界等方面的优越性= =
感觉for的话就是负数不太好处理。
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 int i,j,k,n,m; 7 int s[19],len;ll tmp,l,r; 8 ll f[19][19][1600],ten[19]; 9 bool u[19][19][1600]; 10 11 inline ll dfs(int x,int pos,int pre,bool limit){//考虑剩下的x位数字,支点已确定在pos 12 if(!x||pre<0) return pre==0; 13 if(!limit&&u[x][pos][pre])return f[x][pos][pre]; 14 15 int mx=limit?s[x]:9;ll ans=0; 16 for(int i=0;i<=mx;i++) 17 ans+=dfs(x-1,pos,pre-i*(pos-x),limit&&(i==mx)); 18 if(!u[x][pos][pre]&&!limit) 19 f[x][pos][pre]=ans,u[x][pos][pre]=1; 20 return ans; 21 } 22 inline ll get(ll x){ 23 ll ans=0; 24 for(int i=0;i<=18;i++) 25 if(x==ten[i]){x--,ans++;break;} 26 if(x<0)return 0; 27 28 for(len=0,tmp=x;tmp;tmp/=10)s[++len]=tmp%10; 29 for(int i=len;i;i--) 30 ans+=dfs(len,i,0,1); 31 return ans-len+1; 32 } 33 int main(){ 34 for(i=ten[0]=1;i<=18;i++)ten[i]=ten[i-1]*10; 35 int T; 36 for(scanf("%d",&T);T;T--){ 37 scanf("%lld%lld",&l,&r); 38 printf("%lld\n",get(r)-get(l-1)); 39 } 40 return 0; 41 }
UPD:其实以上写法中,f 数组的“以数字j开头”的那维都是不必要的。。。(所以我个傻逼预处理比别人多了两重循环TAT