[kuangbin带你飞]专题十五 数位DP
62 / 175 Problem A CodeForces 55D Beautiful numbers
一个美丽数就是可以被它的每一位的数字整除的数。
给定一个区间,求美丽数的个数。
这题是很好的数位DP。
比较难想状态。
就是被每一个数字的LCM整除。
1~9的LCM最大是2520,其实也只有48个。
然后dp[i][j][k]表示处理到数位i,该数对2520取模为j,各个数位的LCM为k
/* * 题意:求区间[x , y]中beautiful number的个数, * a positive integer number is beautiful if and only * if it is divisible by each of its nonzero digits. 分析:一个数能被它的所有非零数位整除,则能被它们的最小公倍数整除,而1到9的最小公倍数为2520, 数位DP时我们只需保存前面那些位的最小公倍数就可进行状态转移,到边界时就把所有位的lcm求出了, 为了判断这个数能否被它的所有数位整除,我们还需要这个数的值,显然要记录值是不可能的,其实我们只 需记录它对2520的模即可,这样我们就可以设计出如下数位DP:dfs(pos,mod,lcm,f),pos为当前 位,mod为前面那些位对2520的模,lcm为前面那些数位的最小公倍数,f标记前面那些位是否达到上限, 这样一来dp数组就要开到19*2520*2520,明显超内存了,考虑到最小公倍数是离散的,1-2520中可能 是最小公倍数的其实只有48个,经过离散化处理后,dp数组的最后一维可以降到48,这样就不会超了。 */ #include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int MAXN=25; const int MOD=2520;//1~9的lcm为2520 long long dp[MAXN][MOD][48]; int index[MOD+10];//记录1~9的最小公倍数 int bit[MAXN]; int gcd(int a,int b) { if(b==0)return a; else return gcd(b,a%b); } int lcm(int a,int b) { return a/gcd(a,b)*b; } void init() { int num=0; for(int i=1;i<=MOD;i++) if(MOD%i==0) index[i]=num++; } long long dfs(int pos,int preSum,int preLcm,bool flag) { if(pos==-1) return preSum%preLcm==0; if(!flag && dp[pos][preSum][index[preLcm]]!=-1) return dp[pos][preSum][index[preLcm]]; long long ans=0; int end=flag?bit[pos]:9;//上界 for(int i=0;i<=end;i++) { int nowSum=(preSum*10+i)%MOD; int nowLcm=preLcm; if(i)nowLcm=lcm(nowLcm,i); ans+=dfs(pos-1,nowSum,nowLcm,flag && i==end); } if(!flag)dp[pos][preSum][index[preLcm]]=ans; return ans; } long long calc(long long x) { int pos=0; while(x) { bit[pos++]=x%10; x/=10; } return dfs(pos-1,0,1,1); } int main() { int T; long long l,r; init(); memset(dp,-1,sizeof(dp)); scanf("%d",&T); while(T--) { scanf("%I64d%I64d",&l,&r); printf("%I64d\n",calc(r)-calc(l-1)); } return 0; }
30 / 84 Problem B HDU 4352 XHXJ's LIS
108 / 195 Problem C HDU 2089 不要62
d.求n~m间的数中,多少不带4和62的数。
n、m(0<n≤m<1000000)
s.见注释
#include<iostream> #include<stdio.h> using namespace std; long long dp[10][3]; /* i位数时,相应情况的数字总数 dp[i][0],不含有不吉利数字 dp[i][1],不含有不吉利数字,最高位为2 dp[i][2],含有不吉利数字 */ void init(){ dp[0][0]=1; dp[0][1]=dp[0][2]=0; int i; for(i=1;i<=6;++i){ dp[i][0]=9*dp[i-1][0]-dp[i-1][1];//不含不吉利数字前面加除4外9个数,减掉2前面加6 dp[i][1]=dp[i-1][0];//不含不吉利数字前面加2 dp[i][2]=10*dp[i-1][2]+dp[i-1][0]+dp[i-1][1];//含不吉利数字前面加0~9,加上不含不吉利数字前加4,加上2前面加6 } } int bit[10]; int calc(int n){ int len=0,i,tmp=n; while(n){ bit[++len]=n%10; n/=10; } bit[len+1]=0; bool flag=false;//前缀出现4或者62了吗 long long ans=0; for(i=len;i>=1;--i){ ans+=dp[i-1][2]*bit[i];//注意这个位置,求的是前缀+当前位置(0~bit[i]-1正好是bit[i]种,当前位是不能为bit[i]的) if(flag)ans+=dp[i-1][0]*bit[i];//此时当前位为0~bit[i]-1时,后面不含不吉利数字的全都不行 else{//有3种情况 if(bit[i]>4)ans+=dp[i-1][0];//这位为4的时候(ps:为什么不是>=4?因为只有>4时才能保证当前位取4时,后面的数全都能取到,下同) if(bit[i+1]==6&&bit[i]>2)ans+=dp[i][1];//上位为6,这位为2的时候 if(bit[i]>6)ans+=dp[i-1][1];//这位为6的时候 } if(bit[i]==4||bit[i+1]==6&&bit[i]==2)flag=true;//前缀出现的4或者62了。 } if(flag)++ans;//加上n本身,这个数也不行 return tmp-ans; } int main(){ init(); int n,m; while(scanf("%d%d",&n,&m)){ if(n==0&&m==0)break; printf("%d\n",calc(m)-calc(n-1)); } return 0; }
89 / 222 Problem D HDU 3555 Bomb
d.求1到n有多少个数中含有49,1<=n<=2^63-1(2^32是10位,2^64约20位)
s.数位dp,和上个题类似,少了个条件
dp[i][0],长度为i,不含有49的个数
dp[i][1],长度为i,不含有49,最高位为9的个数
dp[i][2],长度为i,含有49的个数
状态转移方程:
dp[i][0]=10*dp[i-1][0]-dp[i-1][1];//不含49的前面加0~9,减掉9前面加4
dp[i][1]=dp[i-1][0];//不含49的前面加9
dp[i][2]=10*dp[i-1][2]+dp[i-1][1];//含49的前面加0~9,加上9前面加4
求解:从最高位开始遍历,每一位求的都是 前缀+小于当前位 的符合条件的个数。
1.首先加上当前位置之后包含49的个数,因为当前为可以填0~bit[i]-1,所以乘以bit[i],ans+=dp[i-1][2]*bit[i];//注意这个位置,求的是前缀+当前位置
2.前面的前缀出现49了,ans+=dp[i-1][0]*bit[i];//
3.前缀没有出现49,并且当前位>4,ans+=dp[i-1][1];
#include<iostream> #include<stdio.h> using namespace std; long long dp[25][3]; /* dp[i][0],不含有49 dp[i][1],不含有49,最高位为9 dp[i][2],含有49 */ void init(){ dp[0][0]=1; dp[0][1]=dp[0][2]=0; int i; for(i=1;i<25;++i){ dp[i][0]=10*dp[i-1][0]-dp[i-1][1];//不含49的前面加0~9,减掉9前面加4 dp[i][1]=dp[i-1][0];//不含49的前面加9 dp[i][2]=10*dp[i-1][2]+dp[i-1][1];//含49的前面加0~9,加上9前面加4 } } int bit[25]; long long calc(long long n){ int len=0,i; while(n){ bit[++len]=n%10; n/=10; } bit[len+1]=0; bool flag=false; long long ans=0; for(i=len;i>=1;--i){ ans+=dp[i-1][2]*bit[i];//注意这个位置,求的是前缀+当前位置 if(flag)ans+=dp[i-1][0]*bit[i]; else if(bit[i]>4)ans+=dp[i-1][1]; if(bit[i+1]==4&&bit[i]==9)flag=true; } if(flag)++ans;//加上n本身 return ans; } int main(){ init(); int t; long long n; scanf("%d",&t); while(t--){ scanf("%lld",&n); printf("%I64d\n",calc(n)); } return 0; }
59 / 107 Problem E POJ 3252 Round Numbers
d.Round Numbers 就是一个表示成二进制的时候0比1多或者相等的正数,注意是正数,所以0就肯定不是了。
题目是给定一个区间,问在这个区间上的Round Numbers有多少个?
首先是求出小于等于n的Round Numbers有多少个。
s.我先举个例子来先说明,再来说一般方法。
比如: 22 = 10110b 如果要求 <=22的Round Numbers,也就是找出1-22有多少个二进制的0不少于1的数的个数。
22的二进制长度是5.
首先找长度比5小的Round Numbers(长度比5小的数肯定小于22啦)
长度为4的话,第一位必须是1,后面三位的话,可以有2个0,3个0
所以就是C(3,2)+C(3,3);
长度为3的Round Numbers,同理有 C(2,2);//注意不要把第一位1忘记了
长度为2的Round Numbers,有 C(1,1)
长度为1的Round Numbers,有 0个
下面是找长度和22相同的Round Numbers。
首先第一位是1.
22的第二位是0,所以第二位不能为1,必须是0
第三位为0的话,(前面有了2个0,1个1),后面两位可以有1个0,2个0
C(2,1)+C(2,2)
接下来把第三位恢复为1,看第四位。假如第四位是0,(前面有2个0,2个1),后面一位必须是0 C(1,1)
所以大致求的过程就如上面所述。
首先先推个公式,就是长度为len的Round Numbers的个数。
长度为len,第一位肯定是1了。
那么后面剩下 len-1位。
如果len-1是偶数。
那么 C(len-1,(len-1)/2+1)+C(len-1,(len-1)/2+2)+````C(len-1,len-1)
= ( 2^(len-1)-C(len-1,(len-1)/2) )/2;
如果len是奇数
那么就是 ( 2^(len-1) )/2
所以上面求比N长度小的Round Numbers是很好求的了。
至于求长度的,则是逐渐把每一位1变为0,去求后面的,就可以保证比n小了。
看代码吧。很容易理解的。
#include<iostream> #include<stdio.h> using namespace std; int C[33][33]; void init(){ C[0][0]=1; C[1][0]=1;C[1][1]=1; int i,j; for(i=2;i<33;++i){ C[i][0]=1; for(j=1;j<i;++j){ C[i][j]=C[i-1][j-1]+C[i-1][j]; } C[i][i]=1; } } int bits[33]; int calc(int n){////求小于等于n的 Round Numbers if(n==0){// return 0; } int len; len=0; while(n>0){ if(n&1){ bits[len++]=1; } else{ bits[len++]=0; } n>>=1; } int ans; ans=0; int i; //求出长度1~len-1的所有情况 for(i=len-1;i>0;--i){ if(i&1){ ans=ans+((1<<(i-1))-C[i-1][(i-1)/2])/2; } else{ ans=ans+(1<<(i-1))/2; } } int cnt0,cnt1;//前缀出现0的个数和1的个数 cnt0=0; cnt1=1; int j; //长度为len的情况,小于n的 for(i=len-2;i>=0;--i){ if(bits[i]==1){//后面有i位,当前第i位当成0 for(j=i;j>=0&&cnt0+1+j>=cnt1+i-j;--j){//后面i位中选j个0 ans=ans+C[i][j]; } ++cnt1; } else{ ++cnt0; } } //看看原来这个数n cnt0=0; cnt1=0; for(i=0;i<len;++i){ if(bits[i]==0){ ++cnt0; } else{ ++cnt1; } } if(cnt0>=cnt1){ ++ans; } return ans; } int main(){ init(); int a,b; while(~scanf("%d%d",&a,&b)){ printf("%d\n",calc(b)-calc(a-1)); } return 0; }
47 / 75 Problem F HDU 3709 Balanced Number
找出区间内平衡数的个数,所谓的平衡数,就是以这个数字的某一位为支点,另外两边的数字大小乘以力矩之和相等,即为平衡数
/* * HDU 3709 * 平衡数,枚举支点 * dp[i][j][k] i表示处理到的数位,j是支点,k是力矩和 */ #include <iostream> #include <stdio.h> #include <algorithm> #include <string.h> using namespace std; long long dp[20][20][2000]; int bit[20]; long long dfs(int pos,int center,int pre,bool flag) { if(pos==-1)return pre==0; if(pre<0)return 0;//当前力矩为负,剪枝 if(!flag&&dp[pos][center][pre]!=-1) return dp[pos][center][pre]; int end=flag?bit[pos]:9; long long ans=0; for(int i=0;i<=end;i++) ans+=dfs(pos-1,center,pre+i*(pos-center),flag&&i==end); if(!flag)dp[pos][center][pre]=ans; return ans; } long long calc(long long n) { int len=0; while(n) { bit[len++]=n%10; n/=10; } long long ans=0; for(int i=0;i<len;i++) ans+=dfs(len-1,i,0,1); return ans-(len-1);//去掉全0的情况 } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt","w",stdout); int T; long long x,y; memset(dp,-1,sizeof(dp));//这个初始化一定别忘记 scanf("%d",&T); while(T--) { scanf("%I64d%I64d",&x,&y); printf("%I64d\n",calc(y)-calc(x-1)); } return 0; }
64 / 95 Problem G HDU 3652 B-number
找出1~n范围内含有13并且能被13整除的数字的个数
/* * HDU 3652 B-number * 含有数字13和能够被13整除的数的个数 * dp[i][j][k][z]:i:处理的数位,j:该数对13取模以后的值,k:是否已经包含13,z结尾的数 */ #include <iostream> #include <string.h> #include <algorithm> #include <stdio.h> using namespace std; int dp[12][15][2][10]; int bit[12]; int dfs(int pos,int num,bool t,int e,bool flag) { if(pos==-1)return t&&(num==0); if(!flag && dp[pos][num][t][e]!=-1) return dp[pos][num][t][e]; int end=flag?bit[pos]:9; int ans=0; for(int i=0;i<=end;i++) ans+=dfs(pos-1,(num*10+i)%13,t||(e==1&&i==3),i,flag&&(i==end)); if(!flag)dp[pos][num][t][e]=ans; return ans; } int calc(int n) { int pos=0; while(n) { bit[pos++]=n%10; n/=10; } return dfs(pos-1,0,0,0,1); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int n; memset(dp,-1,sizeof(dp)); while(scanf("%d",&n)==1) { printf("%d\n",calc(n)); } return 0; }
42 / 124 Problem H HDU 4734 F(x)
定义十进制数x的权值为f(x) = a(n)*2^(n-1)+a(n-1)*2(n-2)+...a(2)*2+a(1)*1,a(i)表示十进制数x中第i位的数字。
题目给出a,b,求出0~b有多少个权值不大于f(a)的数。
dp[i][j]表示i位值<=j 的总数
/* *********************************************** Author :kuangbin Created Time :2013/9/14 星期六 12:45:42 File Name :2013成都网络赛\1007.cpp ************************************************ */ #pragma comment(linker, "/STACK:1024000000,1024000000") #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <math.h> #include <stdlib.h> #include <time.h> using namespace std; int dp[20][200000]; int bit[20]; int dfs(int pos,int num,bool flag) { if(pos == -1)return num >= 0; if(num < 0)return 0; if(!flag && dp[pos][num] != -1) return dp[pos][num]; int ans = 0; int end = flag?bit[pos]:9; for(int i = 0;i <= end;i++) { ans += dfs(pos-1,num - i*(1<<pos),flag && i==end); } if(!flag)dp[pos][num] = ans; return ans; } int F(int x) { int ret = 0; int len = 0; while(x) { ret += (x%10)*(1<<len); len++; x /= 10; } return ret; } int A,B; int calc() { int len = 0; while(B) { bit[len++] = B%10; B/=10; //cout<<bit[len-1]<<endl; } //cout<<F(A)<<endl; return dfs(len-1,F(A),1); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; int iCase = 0; scanf("%d",&T); memset(dp,-1,sizeof(dp)); while(T--) { iCase++; scanf("%d%d",&A,&B); printf("Case #%d: %d\n",iCase,calc()); } return 0; }
11 / 20 Problem I ZOJ 3494 BCD Code
31 / 100 Problem J HDU 4507 吉哥系列故事――恨7不成妻
如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
1、整数中某一位是7;
2、整数的每一位加起来的和是7的整数倍;
3、这个整数是7的整数倍;
要求一个区间中和7无关的数的平方和。
需要用数位DP维护3个值:
1.与7无关的数的个数
2.与7无关的数的和
3、与7无关的数的平方和。
第一个是与7无关的数的个数,就是简单的数位DP了,很常规。
第二个与7无关的数的和的维护需要用到第一个个数。
处理到第pos个数位时,加上i*10^pos * 后面的个数
第三个的维护需要用到前面两个
(pre*10^pos + next)^2= (pre*10^pos)^2+2*pre*10^pos*next +next^2
/* * 如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关—— 1、整数中某一位是7; 2、整数的每一位加起来的和是7的整数倍; 3、这个整数是7的整数倍; 求一个区间中与7无关的数的平方和 */ #include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const long long MOD=1000000007LL; struct Node { long long cnt;//与7无关的数的个数 long long sum;//与7无关的数的和 long long sqsum;//平方和 }dp[20][10][10];//分别是处理的数位、数字和%7,数%7 int bit[20]; long long p[20];//p[i]=10^i Node dfs(int pos,int pre1,int pre2,bool flag) { if(pos==-1) { Node tmp; tmp.cnt=(pre1!=0 && pre2!=0); tmp.sum=tmp.sqsum=0; return tmp; } if(!flag && dp[pos][pre1][pre2].cnt!=-1) return dp[pos][pre1][pre2]; int end=flag?bit[pos]:9; Node ans; Node tmp; ans.cnt=ans.sqsum=ans.sum=0; for(int i=0;i<=end;i++) { if(i==7)continue; tmp=dfs(pos-1,(pre1+i)%7,(pre2*10+i)%7,flag&&i==end); ans.cnt+=tmp.cnt; ans.cnt%=MOD; ans.sum+=(tmp.sum+ ((p[pos]*i)%MOD)*tmp.cnt%MOD )%MOD; ans.sum%=MOD; ans.sqsum+=(tmp.sqsum + ( (2*p[pos]*i)%MOD )*tmp.sum)%MOD; ans.sqsum%=MOD; ans.sqsum+=( (tmp.cnt*p[pos])%MOD*p[pos]%MOD*i*i%MOD ); ans.sqsum%=MOD; } if(!flag)dp[pos][pre1][pre2]=ans; return ans; } long long calc(long long n) { int pos=0; while(n) { bit[pos++]=n%10; n/=10; } return dfs(pos-1,0,0,1).sqsum; } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; long long l,r; p[0]=1; for(int i=1;i<20;i++) p[i]=(p[i-1]*10)%MOD; for(int i=0;i<20;i++) for(int j=0;j<10;j++) for(int k=0;k<10;k++) dp[i][j][k].cnt=-1; scanf("%d",&T); while(T--) { scanf("%I64d%I64d",&l,&r); long long ans=calc(r); ans-=calc(l-1); ans=(ans%MOD+MOD)%MOD; printf("%I64d\n",ans); } return 0; }
42 / 75 Problem K SPOJ BALNUM Balanced Numbers
这题要求出现的数字,偶数出现奇数次,奇数出现偶数次。
用三进制表示0~9的状态
//============================================================================ // Name : SPOJ.cpp // Author : // Version : // Copyright : Your copyright notice // Description : Hello World in C++, Ansi-style //============================================================================ #include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; long long dp[20][60000]; //3进制表示数字0~9的出现情况,0表示没有出现,1表示奇数次,2表示偶数次 int bit[20]; bool check(int s) { int num[10]; for(int i=0;i<10;i++) { num[i]=s%3; s/=3; } for(int i=0;i<10;i++) if(num[i]!=0) { if(i%2==0 && num[i]==2)return false; if(i%2==1 && num[i]==1)return false; } return true; } int getnews(int x,int s) { int num[10]; for(int i=0;i<10;i++) { num[i]=s%3; s/=3; } if(num[x]==0)num[x]=1; else num[x]=3-num[x]; int news=0; for(int i=9;i>=0;i--) { news*=3; news+=num[i]; } return news; } long long dfs(int pos,int s,bool flag,bool z) { if(pos==-1)return check(s); if(!flag && dp[pos][s]!=-1) return dp[pos][s]; long long ans=0; int end=flag?bit[pos]:9; for(int i=0;i<=end;i++) ans+=dfs(pos-1,(z&&i==0)?0:getnews(i,s),flag&&i==end,z&&i==0); if(!flag)dp[pos][s]=ans; return ans; } long long calc(long long n) { int len=0; while(n) { bit[len++]=n%10; n/=10; } return dfs(len-1,0,1,1); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; memset(dp,-1,sizeof(dp)); long long a,b; scanf("%d",&T); while(T--) { cin>>a>>b; cout<<calc(b)-calc(a-1)<<endl; } return 0; }
ps:从这个题发现了pow的精度问题,呵呵
http://blog.csdn.net/u014665013/article/details/70990408