1、CF 55D Beautiful numbers
题意:如果一个数能被自己各个位的数字整除,那么它就叫 Beautiful numbers。求区间 [a,b] 中 Beautiful numbers 的个数。
思路:一个数能被它的所有非零数位整除,则能被它们的最小公倍数整除,而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,这样就不会超了。

1 #include <stdio.h> 2 #include <iostream> 3 #include <map> 4 #include <set> 5 #include <list> 6 #include <stack> 7 #include <vector> 8 #include <math.h> 9 #include <string.h> 10 #include <queue> 11 #include <string> 12 #include <stdlib.h> 13 #include <algorithm> 14 #define LL long long 15 #define eps 1e-12 16 #define PI acos(-1.0) 17 using namespace std; 18 const int INF = 0x3f3f3f3f; 19 const int maxn = 4010; 20 const int max_lcm = 2520; 21 22 LL gcd(LL a, LL b) 23 { 24 if (b == 0) 25 return a; 26 return gcd(b, a%b); 27 } 28 LL lcm(LL a, LL b) 29 { 30 return a / gcd(a, b)*b; 31 } 32 int dig[25]; 33 LL dp[25][50][2525];//经过分析后可以设出dp[20][2050][2050],dp[i][j][k]表示处理到i位,前面的数的最小公倍数为j,前面的数 % 2520为k。因为1~9组成的最小公倍数只有48个,可以离散化,这样数组就降到了dp[20][50][2520]。 34 int Hash[2525]; 35 36 LL dfs(int len, int prelcm, int prenum, int up) 37 { 38 if (len == 0) 39 { 40 return prenum%prelcm == 0; 41 } 42 if (!up && dp[len][Hash[prelcm]][prenum] != -1) 43 return dp[len][Hash[prelcm]][prenum]; 44 int end = up ? dig[len] : 9; 45 LL res = 0; 46 for (int i = 0; i <= end; i++) 47 { 48 int nownum = (prenum * 10 + i) % max_lcm; 49 int nowlcm = prelcm; 50 if (i) 51 nowlcm = lcm(prelcm, i); 52 res += dfs(len - 1, nowlcm, nownum, up&&i == end);//flag&&i==end,在最开始,取出的end是最高位,所以如果i比end小,那么i的下一位都可以到达9,而i==num了,最大能到达的就只有,dig[pos-1] 53 } 54 if (!up) 55 dp[len][Hash[prelcm]][prenum] = res; 56 return res; 57 } 58 59 LL cal(LL num) 60 { 61 int len = 0; 62 while (num) 63 { 64 dig[++len] = num % 10; 65 num /= 10; 66 } 67 return dfs(len, 1, 0, 1); 68 } 69 70 int main() 71 { 72 int test; 73 LL a, b; 74 int cnt = 0; 75 for (int i = 1; i <= 2520; i++) //离散化 76 { 77 if (max_lcm % i == 0) 78 Hash[i] = ++cnt; 79 } 80 81 scanf("%d", &test); 82 memset(dp, -1, sizeof(dp)); 83 for (int item = 1; item <= test; item++) 84 { 85 scanf("%I64d %I64d", &a, &b); 86 printf("%I64d ", cal(b) - cal(a - 1)); 87 } 88 89 return 0; 90 }
2、HDU 4352 XHXJ’s LIS
题意:假设把一个数字当成字符串,将它的最长单调递增子序列长度称为power值,求[L,R]区间内power值等于k(1<=K<=10)的值有多少个。
思路:dp[i][j][k]:i为当前进行到的数位,j状态压缩,为10个数字出现过的,其中1的个数就是最长上升子序列,k要求的上升子序列的长度。

1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #define LL __int64 5 using namespace std; 6 LL dp[40][1<<10][11];//dp[i][j][k]:i为当前进行到的数位,j状态压缩,为10个数字出现过的,其中1的个数就是最长上升子序列,k要求的上升子序列的长度 7 int bit[40], pos; 8 LL L, R; int K; 9 int numone(int state) 10 {//得到1的个数,即最长递增子序列的长度 11 int ret = 0; 12 while (state) 13 { 14 ret += state & 1; state >>= 1; 15 } 16 return ret; 17 } 18 int turn(int state, int x) 19 {//找到第一个大于x的数并且替换他,nlogn求最长递增子序列的思想 20 21 for(int i=x;i<=9;i++) 22 if ((1<<i)&state) 23 { 24 return ((state^(1<<i)) | (1<<x)); 25 } 26 return (state | (1<<x)); 27 } 28 LL DP(int pp, int state, bool nozero, bool big)//has 是否有inc==K 29 {//nozero为前面的是否为0标记,big上界标记 30 if (pp == 0)return numone(state) == K; 31 if (big&&dp[pp][state][K] != -1)return dp[pp][state][K]; 32 LL ret = 0; 33 int kn = big ? 9 : bit[pp]; 34 for(int i=0;i<=kn;i++) 35 { 36 ret += DP(pp - 1, (nozero || i != 0) ? turn(state, i) : 0, nozero || i != 0, big || kn != i); 37 } 38 if (big)dp[pp][state][K] = ret; 39 return ret; 40 } 41 LL get(LL x) 42 { 43 pos = 0; 44 while (x) 45 { 46 bit[++pos] = x % 10; 47 x /= 10; 48 } 49 return DP(pos, 0, 0, 0); 50 } 51 int main() 52 { 53 int t; 54 memset(dp, -1, sizeof(dp)); 55 scanf("%d", &t); 56 int Case = 1; 57 while (t--) 58 { 59 scanf("%I64d%I64d%d", &L, &R, &K); 60 printf("Case #%d: %I64d ", Case++, get(R) - get(L - 1)); 61 } 62 return 0; 63 }