什么是数位 DP
在信息学竞赛中,有一类难度不大但异常麻烦的问题——数位计数问题,这类问题的主要特点是询问的答案和一段连续的数的各个数位相关,并且需要对时间效率有一定要求。由于解决这类问题往往意味着巨大的代码量,而众多的特殊情况又意味着出现错误的巨大可能性,因此很少有人愿意解决此类问题,但只要掌握好的方法,解决这类问题也并非想象中的那样困难。
---------高逸涵《数位计数问题解法研究》
数位DP的解题思路
对于一个数,若其首位已经比询问上界小,则剩余位没有任何限制。此时如果能直接处理这一情况,则问题距离解决又会迈出一大步。
例如,在十进制下,计算[10000,54321]内的数字和,我们可以将其分解为:
[10000,19999],[20000,29999],[30000,39999],[40000,49999],[50000,54321]。
前四个区间如果可以直接解决,则只需处理最后一个区间,进一步将最后一个区间划分为:
[50000,50999],[51000,51999],[52000,52999],[53000,53999],[54000,54321]。
同理将最后一个区间划分下去,最后可以得到以下区间划分:
[10000,19999],[20000,29999],[30000,39999],[40000,49999],
[50000,50999],[51000,51999],[52000,52999],[53000,53999],
[54000,54099],[54100,54199],[54200,54299],
[54300,54309],[54310,54319],
[54320,54321]
数位DP的经典模板
该模板出处不详,大大降低了数位DP类问题的难度。
1 typedef long long LL; 2 const int maxn=22; 3 int dig[maxn]; 4 LL f[maxn]/* [TODO] */; 5 6 LL dfs(int pos,/* TODO */,int limit){ 7 if (pos<0) return /* TODO */; 8 if (!limit&&f[pos]/* [TODO] */!=-1) return f[pos]/* [TODO] */; 9 LL res=0; 10 int last=limit?dig[pos]:9; 11 for (int i=0;i<=last;i++){ 12 res+=dfs(pos-1,/* TODO */,limit&&(i==last)); 13 } 14 if (!limit) f[pos]/* [TODO] */=res; 15 return res; 16 } 17 18 LL solve(LL n){ 19 int len=0; 20 while (n){ 21 dig[len++]=n%10; 22 n/=10; 23 } 24 return dfs(len-1,/* TODO */,1); 25 }
经典题目
HDU 2089 不要62
求给定区间中不含有62和4的数的个数。
1 LL dfs(int pos,int pre,int fg,int limit){ 2 if (pos<0) return fg==0; 3 if (!limit&&f[pos][pre][fg]!=-1) return f[pos][pre][fg]; 4 LL res=0; 5 int last=limit?dig[pos]:9; 6 for (int i=0;i<=last;i++){ 7 res+=dfs(pos-1,i,fg||((pre==6)&&(i==2))||(i==4),limit&&(i==last)); 8 } 9 if (!limit) f[pos][pre][fg]=res; 10 return res; 11 }
HDU 3555 Bomb
求给定区间的含有49的数的个数。
1 LL dfs(int pos,int pre,int istrue,int limit){ 2 if (pos<0) return istrue; 3 if (!limit && f[pos][pre][istrue]!=-1) return f[pos][pre][istrue]; 4 int last=limit?dig[pos]:9; 5 LL ret=0; 6 for (int i=0;i<=last;i++){ 7 int ok=(pre==4)&&(i==9); 8 ret+=dfs(pos-1,i,istrue||ok,limit&&(i==last)); 9 } 10 if (!limit) f[pos][pre][istrue]=ret; 11 return ret; 12 }
windy 数
求给定区间范围内的,求相邻数位之差绝对值不小于2的数的个数。
1 LL dfs(int pos,int pre,int fg,int limit){ 2 if (pos<0) return 1; 3 if (!limit && f[pos][pre][fg]!=-1) return f[pos][pre][fg]; 4 int last=limit?dig[pos]:9; 5 LL ret=0; 6 for (int i=0;i<=last;i++){ 7 if (fg==0||abs(i-pre)>=2) 8 ret+=dfs(pos-1,i,fg||i,limit&&(i==last)); 9 } 10 if (!limit) f[pos][pre][fg]=ret; 11 return ret; 12 }
HDU 3709 Balanced Number
平衡数。数n以数n中的某个位为支点,每个位上的数权值为(数字xi*(posi - 支点的posi)),如果数n里有一个支点使得所有数权值之和为0那么她就是平衡数。比如4139,以3为支点,左边 = 4 * (4 - 2) + 1 * (3 - 2) = 9,右边 = 9 * (1 - 2) = -9,左边加右边为0,所以4139是平衡数。现在给出一个区间[l,r],问区间内平衡数有多少个?
1 LL dfs(int pos,int o,int pre,int limit){ 2 LL res=0; 3 if (pos<0) return pre==0; 4 if (!limit&&f[pos][o][pre]!=-1) return f[pos][o][pre]; 5 int last=limit?dig[pos]:9; 6 for (int i=0;i<=last;i++){ 7 res+=dfs(pos-1,o,pre+i*(pos-o),limit&&(i==last)); 8 } 9 if (!limit) f[pos][o][pre]=res; 10 return res; 11 } 12 13 LL solve(LL n){ 14 if (n<0) return 0; 15 if (n==0) return 1; 16 int len=0; 17 while (n){ 18 dig[len++]=n%10; 19 n/=10; 20 } 21 LL ans=0; 22 for (int i=0;i<len;i++){ 23 ans+=dfs(len-1,i,0,1); 24 } 25 ans=ans-len+1; 26 return ans; 27 }
CodeForces 55D Beautiful numbers
如果一个数能够被其每个数位的数都整除,那么这个数就叫做美丽数。
1 int check(int bit,int mod){ 2 for (int i=2;i<=9;i++){ 3 if (bit&(1<<(i-2))){ 4 if (mod%i) return 0; 5 } 6 } 7 return 1; 8 } 9 10 LL dfs(int pos,int bit,int mod,int limit){ 11 if (pos<0) return check(bit,mod); 12 if (!limit && f[pos][bit][mod]!=-1) return f[pos][bit][mod]; 13 int last=limit?dig[pos]:9; 14 LL ret=0; 15 for (int i=0;i<=last;i++){ 16 int nbit=bit; 17 if (i>=2) nbit|=1<<(i-2); 18 ret+=dfs(pos-1,nbit,(mod*10+i)%MOD,limit&&(i==last)); 19 } 20 if (!limit) f[pos][bit][mod]=ret; 21 return ret; 22 }
HDU 3652 B-number
求小于n是13的倍数且含有'13'的数的个数。
1 LL dfs(int pos,int pre,int fg,int md,int limit){ 2 if (pos<0) return fg&&(md==0); 3 if (!limit&&f[pos][pre][fg][md]!=-1) return f[pos][pre][fg][md]; 4 LL res=0; 5 int last=limit?dig[pos]:9; 6 for (int i=0;i<=last;i++){ 7 res+=dfs(pos-1,i,fg||(pre==1&&i==3),(md*10+i)%13,limit&&(i==last)); 8 } 9 if (!limit) f[pos][pre][fg][md]=res; 10 return res; 11 }
HDU 4352 XHXJ's LIS
求[L,R]内最长递增子序列是k的数的个数。
1 LL f[maxn][1<<10][11]; 2 int K; 3 int dig[maxn]; 4 int getLIS(int bit){ 5 int res=0; 6 for (int i=0;i<10;i++){ 7 if (bit&(1<<i)) res++; 8 } 9 return res; 10 } 11 int gaoBit(int bit,int d){ 12 if (bit&(1<<d)) return bit; 13 if ((1<<d)>bit) return bit|(1<<d); 14 bit|=(1<<d); 15 for (int i=d+1;i<10;i++){ 16 if (bit&(1<<i)) return bit^(1<<i); 17 } 18 return 0; 19 } 20 21 LL dfs(int pos,int bit,int limit){ 22 if (pos<0) return getLIS(bit)==K; 23 if (!limit&&f[pos][bit][K]!=-1) return f[pos][bit][K]; 24 LL res=0; 25 int last=limit?dig[pos]:9; 26 for (int i=0;i<=last;i++){ 27 28 int go; 29 if (bit==0&&i==0) go=0; 30 else go=gaoBit(bit,i); 31 res+=dfs(pos-1,go,limit&&(last==i)); 32 } 33 if (!limit) f[pos][bit][K]=res; 34 return res; 35 } 36 37 LL solve(LL n){ 38 int len=0; 39 while (n){ 40 dig[len++]=n%10; 41 n/=10; 42 } 43 return dfs(len-1,0,1); 44 }
HDU 4507 吉哥系列故事——恨7不成妻
中文题。需要推公式。
1 #include <iostream> 2 #include <cstring> 3 4 using namespace std; 5 typedef long long LL; 6 const int maxn=22; 7 const int MOD=1e9+7; 8 9 10 typedef pair<LL,LL> PII; 11 typedef pair<PII,LL> PIII; 12 13 inline LL fst(PIII a){ 14 return a.first.first; 15 } 16 inline LL sec(PIII a){ 17 return a.first.second; 18 } 19 inline LL thd(PIII a){ 20 return a.second; 21 } 22 inline PIII makeZeroPIII(){ 23 return make_pair(make_pair(0,0),0); 24 } 25 inline PIII makeOnePIII(){ 26 return make_pair(make_pair(1,0),0); 27 } 28 LL power[maxn]; 29 PIII f[maxn][11][8]; 30 bool v[maxn][11][8]; 31 int dig[maxn]; 32 33 PIII gao(PIII a, PIII b,int i,int pos){ 34 PIII res=makeZeroPIII(); 35 res.first.first=(fst(a)+fst(b))%MOD;//数量 36 res.first.second=(sec(a)+sec(b)+((i*power[pos])%MOD*fst(b))%MOD)%MOD;//和 37 res.second=(thd(a)+thd(b)+((2*i*power[pos])%MOD*sec(b))%MOD+(((i*i*power[pos])%MOD*power[pos])%MOD*fst(b))%MOD)%MOD;//平方和 38 return res; 39 } 40 41 PIII dfs(int pos,int pre,int sev,int limit){ 42 if (pos<0) { 43 if (pre!=0&&sev!=0) return makeOnePIII(); 44 else return makeZeroPIII(); 45 } 46 if (!limit&&v[pos][pre][sev]) return f[pos][pre][sev]; 47 PIII res=makeZeroPIII(); 48 int last=limit?dig[pos]:9; 49 for (int i=0;i<=last;i++){ 50 if (i==7) continue; 51 PIII tmp=dfs(pos-1,(pre*10+i)%7,(sev+i)%7,limit&&(i==last)); 52 res=gao(res,tmp,i,pos); 53 } 54 if (!limit){ 55 v[pos][pre][sev]=true; 56 f[pos][pre][sev]=res; 57 } 58 return res; 59 } 60 61 LL solve(LL n){ 62 int len=0; 63 while (n){ 64 dig[len++]=n%10; 65 n/=10; 66 } 67 PIII ans = dfs(len-1,0,0,1); 68 return thd(ans); 69 } 70 71 int main() 72 { 73 memset(v,0,sizeof(v)); 74 memset(power,0,sizeof(power)); 75 power[0]=1; 76 for (int i=1;i<maxn;i++){ 77 power[i]=(power[i-1]*10)%MOD; 78 } 79 int T; 80 cin>>T; 81 while (T--){ 82 LL a,b; 83 cin>>a>>b; 84 cout<<(solve(b)-solve(a-1)+MOD)%MOD<<endl; 85 } 86 return 0; 87 }