数位DP

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\n", cal(b) - cal(a - 1));
87     }
88 
89     return 0;
90 }
View Code

 

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\n", Case++, get(R) - get(L - 1));
61     }
62     return 0;
63 }
View Code

 

 

posted @ 2017-08-15 22:37  萌萌的美男子  阅读(251)  评论(0编辑  收藏  举报