数位DP专题

这周开始刷数位DP,在网上找到一份神级数位DP模板,做起题目来爽歪歪。

http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html

1 int dfs(int i, int s, bool e) {
2     if (i==-1) return s==target_s;
3     if (!e && ~f[i][s]) return f[i][s];
4     int res = 0;
5     int u = e?num[i]:9;
6     for (int d = first?1:0; d <= u; ++d)
7         res += dfs(i-1, new_s(s, d), e&&d==u);
8     return e?res:f[i][s]=res;
9 }

f为记忆化数组;

i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);

s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);

e表示之前的数是否是上界的前缀(即后面的数能否任意填)。

for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。It depends.

于是关键就在怎么设计状态。当然做多了之后状态一眼就可以瞄出来。

HDU2098 不要62

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 15
11 int dp[maxn][2],num[maxn];
12 int new_s(int s, int d)
13 {
14     if (d == 6) return 1;
15     else
16     {
17         return 0;
18     }
19 }
20 int dfs(int i, int s, bool e) {
21     if (i == -1) return 1;
22     if (!e && ~dp[i][s]) return dp[i][s];
23     int res = 0;
24     int u = e ? num[i] : 9;
25     for (int d = 0; d <= u; ++d)
26     {
27         if (d==4) continue;
28         if (s&&d==2) continue;
29         res += dfs(i - 1, new_s(s, d), e && d == u);
30     }
31     return e ? res : dp[i][s] = res;
32 }
33 int cal(int n)
34 {
35     int cnt = 0;
36     while (n)
37     {
38         num[cnt++] = n % 10;
39         n /= 10;
40     }
41     return dfs(cnt - 1, 0,1);
42 }
43 int main()
44 {
45     int l, r;
46     memset(dp, -1, sizeof(dp));
47     while (scanf("%d%d", &l, &r) != EOF&&l+r)
48     {
49         printf("%d\n", cal(r) - cal(l -1));
50     }
51     return 0;
52 }
View Code

HDU3555 Bomb

和上题差不多

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 30
11 typedef long long LL;
12 LL dp[maxn][3];
13 int num[maxn];
14 int new_s(int s, int d)
15 {
16     if (s == 2) return 2;
17     if (s == 1 && d == 9) return 2;
18     if (d == 4) return 1;
19     return 0;
20 
21 }
22 LL dfs(int i, int s, bool e)
23 {
24     if (i == -1) return s == 2;
25     if (!e&&~dp[i][s]) return dp[i][s];
26     LL ret = 0;
27     int u = e ? num[i] : 9;
28     for (int d = 0; d <= u; d++)
29         ret += dfs(i - 1, new_s(s, d), e&&d == u);
30     return e ? ret : dp[i][s] = ret;
31 
32 }
33 LL cal(LL n)
34 {
35     int cnt = 0;
36     while (n)
37     {
38         num[cnt++] = n % 10;
39         n /= 10;
40     }
41     return dfs(cnt - 1, 0, 1);
42 }
43 int main()
44 {
45     int T;
46     scanf("%d", &T);
47     memset(dp, -1, sizeof(dp));
48     while (T--)
49     {
50         LL n;
51         scanf("%I64d", &n);
52         printf("%I64d\n", cal(n));
53     }
54     return 0;
55 }
View Code

BZOJ1026 windy数

注意前导0

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 30
11 typedef long long LL;
12 LL dp[maxn][10];
13 int num[maxn];
14 LL dfs(int i, int s, bool e,int pre)
15 {
16     if (i == -1) return s == 1;
17     if (!e&&~dp[i][pre]&&s) return dp[i][pre];
18     LL ret = 0;
19     int u = e ? num[i] : 9;
20     for (int d = 0; d <= u; d++)
21     if(!s||abs(pre-d)>=2)
22     {
23         ret += dfs(i - 1, s||d>0, e&&d == u, d);
24     }
25     if (!e&&s)dp[i][pre] = ret;
26     return ret;
27 }
28     
29 LL cal(LL n)
30 {
31     int cnt = 0;
32     while (n)
33     {
34         num[cnt++] = n % 10;
35         n /= 10;
36     }
37     return dfs(cnt - 1, 0, 1,11);
38 }
39 int main()
40 {
41     LL x,y;
42     memset(dp, -1, sizeof(dp));
43     while (scanf("%lld%lld", &x,&y)!=EOF)
44     {
45         printf("%lld\n", cal(y)-cal(x-1));
46     }
47     return 0;
48 }
View Code

HDU3652 B-number

加一维,记录余数

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 30
11 typedef long long LL;
12 LL dp[maxn][15][3];
13 int num[maxn];
14 int new_s(int s, int d)
15 {
16     if (s == 2) return 2;
17     if (s == 1 && d == 3) return 2;
18     if (d == 1) return 1;
19     return 0;
20 }
21 LL dfs(int i, int s, bool e,int r)
22 {
23     if (i == -1)
24     {
25         if ((s== 2) && (r== 0)) return 1;
26         else return 0;
27     }
28     if (!e&&~dp[i][r][s]) return dp[i][r][s];
29     LL ret = 0;
30     int u = e ? num[i] : 9;
31     for (int d = 0; d <= u; d++)
32     {
33         ret += dfs(i - 1,new_s(s,d) , e&&d == u, (r*10+d)%13);
34     }
35     return e ? ret : dp[i][r][s] = ret;
36 }
37     
38 LL cal(LL n)
39 {
40     int cnt = 0;
41     while (n)
42     {
43         num[cnt++] = n % 10;
44         n /= 10;
45     }
46     return dfs(cnt - 1, 0, 1,0);
47 }
48 int main()
49 {
50     LL n;
51     memset(dp, -1, sizeof(dp));
52     while (scanf("%I64d",&n)!=EOF)
53     {
54         printf("%I64d\n", cal(n));
55     }
56     return 0;
57 }
View Code

HDU3943 K-th Nya Number

需要二分答案,还有就是注意区间范围是[P+1,Q],被坑了好多发,还有我很不解的就是,memset放在最外面就会WA,每输入一组案例清空一次就AC了。。坑爹。。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 25
11 typedef long long LL;
12 LL dp[maxn][maxn][maxn];
13 LL P, Q, x, y;
14 int num[maxn];
15 LL dfs(int i, int sx, int sy, bool e)
16 {
17     if (i == -1)
18     {
19         if (sx == x&&sy == y) return 1; 
20         else return 0;
21     }
22     if (!e&&~dp[i][sx][sy]) return dp[i][sx][sy];
23     LL ret = 0;
24     int u = e ? num[i] : 9;
25     for (int d = 0; d <= u; d++)
26     {
27         if (sx == x&&d == 4) continue;
28         if (sy == y&&d == 7) continue;
29         int a, b;
30         a = sx; b = sy;
31         if (d == 4) a++;
32         if (d == 7) b++;
33         ret += dfs(i - 1, a, b, e&&d == u);
34     }
35     return e ? ret : dp[i][sx][sy] = ret;
36 }
37 LL cal(LL n)
38 {
39     int cnt = 0;
40     while (n)
41     {
42         num[cnt++] = n % 10;
43         n /= 10;
44     }
45     return dfs(cnt - 1, 0, 0, 1);
46 }
47 LL Bin(LL k)
48 {
49     LL l, r, mid, ans,ret;
50     ans = 0;
51     ret = cal(P);
52     l = P+1; r = Q;
53     while (l <= r)
54     {
55         mid = (l + r) >> 1;
56         if (cal(mid) - ret>= k)
57         {
58             ans = mid;
59             r = mid - 1;
60         }
61         else l = mid + 1;
62     }
63     return ans;
64 }
65 int main()
66 {
67     int T,kase=0;
68     scanf("%d", &T);
69     
70     while (T--)
71     {
72         int n;
73         memset(dp, -1, sizeof(dp));
74         scanf("%I64d%I64d%I64d%I64d", &P, &Q, &x, &y);
75         scanf("%d", &n);
76         printf("Case #%d:\n", ++kase);
77         while (n--)
78         {
79             LL k;
80             scanf("%I64d",&k);
81             LL ans = Bin(k);
82             if (ans)
83             printf("%I64d\n", ans);
84             else printf("Nya!\n");
85         }
86     }
87     return 0;
88 }
View Code

POJ3208 Apocalypse Someday

做法和上题一样,需要注意的是它是必须有连续的三个6,还有就是二分的上界尽量大。。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 30
11 typedef long long LL;
12 LL dp[maxn][4];
13 int num[maxn];
14 int new_d(int s, int d)
15 {
16     if (s == 3) return 3;
17     int st = s;
18     if (d == 6) s++;
19     return st==s?0:s;
20 }
21 LL dfs(int i, int s,bool e)
22 {
23     if (i == -1) return s == 3;
24     if (!e&&~dp[i][s]) return dp[i][s];
25     LL ret = 0;
26     int u = e ? num[i] : 9;
27     for (int d = 0; d <= u; d++)
28     {
29         ret += dfs(i - 1, new_d(s,d),e&&d == u);
30     }
31     return e ? ret : dp[i][s] = ret;
32 }
33 LL cal(LL n)
34 {
35     int cnt = 0;
36     while (n)
37     {
38         num[cnt++] = n % 10;
39         n /= 10;
40     }
41     return dfs(cnt - 1, 0, 1);
42 }
43 LL Bin(LL k)
44 {
45     LL l, r, mid, ans,ret;
46     ans = 0;
47     l = 666, r = 100000000000LL;
48     while (l <= r)
49     {
50         mid = (l + r) >> 1;
51         if (cal(mid)>= k)
52         {
53             ans = mid;
54             r = mid - 1;
55         }
56         else l = mid + 1;
57     }
58     return ans;
59 }
60 int main()
61 {
62     int T;
63     scanf("%d", &T);
64     while (T--)
65     {
66         LL k;
67         memset(dp, -1, sizeof(dp));
68         scanf("%I64d", &k);
69         printf("%I64d\n", Bin(k));
70     }
71     return 0;
72 }
View Code

SPOJ BALNUM Balanced Numbers

刚开始一直不知道该怎么记录前面的状态,搜了下解题报告,用的是三进制来表示前面的状态(此题的精华就是这里吧。。),因为状态总数为3^10,因此也不大。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 66666
11 typedef long long LL;
12 LL dp[25][maxn];
13 int num[25];
14 int cnt[10];
15 void go(int s)
16 {
17     for (int i = 0; i < 10; i++)
18     {
19         cnt[i] = s % 3;
20         s /= 3;
21     }
22 }
23 int new_s(int s, int d)
24 {
25     go(s);
26     if (cnt[d] == 0) cnt[d] = 1;
27     else
28         cnt[d] = 3 - cnt[d];
29     int base = 1;
30     s = 0;
31     for (int i = 0; i < 10; i++)
32     {
33         s += base * cnt[i];
34         base *= 3;
35     }
36     return s;
37 }
38 int check(int s)
39 {
40     go(s);
41     for (int i = 0; i < 10; i++)
42     {
43         if ((i & 1) && (cnt[i] == 1)) return 0;
44         if (!(i & 1) && (cnt[i] == 2))return 0;
45     }
46     return 1;
47 }
48 LL dfs(int i, int s, bool e,int zero)
49 {
50     if (i == -1) return check(s);
51     if (!e&&~dp[i][s]) return dp[i][s];
52     LL ret = 0;
53     int u = e ? num[i] : 9;
54     for (int d = 0; d <= u; d++)
55     {
56         ret += dfs(i - 1, zero&&d==0?0:new_s(s, d), e&&d == u,zero&&d==0);
57     }
58     return e ? ret : dp[i][s] = ret;
59 }
60 LL cal(LL n)
61 {
62     int cnt = 0;
63     while (n)
64     {
65         num[cnt++] = n % 10;
66         n /= 10;
67     }
68     return dfs(cnt - 1, 0, 1,1);
69 }
70 int main()
71 {
72     int T;
73     scanf("%d", &T);
74     memset(dp, -1, sizeof(dp));
75     while (T--)
76     {
77         LL x, y;
78         scanf("%lld%lld", &x, &y);
79         printf("%lld\n", cal(y) - cal(x - 1));
80     }
81     return 0;
82 }
View Code

SPOJ MYQ10 Mirror Number

每个数字只可能是0,1,8,区间比较大0 <= a<=b <= 10^44,所以输入要用字符串,一般我们求答案都是:cal(b)-cal(a-1),但此题是字符串,因此需要特殊下a是不是Mirror Number,还被坑了好久的就是,由于是字符串输入,最高位的下标是0。。然后我没有反转过来。。找了好久才发现。。用一个数组记录前面选的数(之前的状态),只要第一个非0位确定就可以知道回文串的长度,也就知道回文串中心的位置,然后从中心更低的位置开始判断是不是回文。前导0也需要注意

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <utility>
 6 #include <vector>
 7 #include <queue>
 8 using namespace std;
 9 #define INF 0x3f3f3f3f
10 #define maxn 50
11 typedef long long LL;
12 LL dp[maxn][maxn];
13 int num[maxn],tmp[maxn];
14 LL dfs(int i, int len, bool e,int zero)
15 {
16     if (i == -1) return 1;
17     if (!e&&~dp[i][len]) return dp[i][len];
18     LL ret = 0;
19     int u = e ? num[i] : 9;
20     for (int d = 0; d <= u; d++)
21     {
22         if (d!=0&&d!=1&&d!=8) continue;
23         if (zero)
24         {
25             tmp[i] = d;
26             ret += dfs(i - 1, len - !d, e&&d == u, zero&& d == 0);
27         }
28         else
29         {
30             int mid = len / 2;
31             int fg = i < mid ? 1 : 0;
32             if (fg)
33             {
34                 if (tmp[len - i - 1] == d)
35                     ret += dfs(i - 1, len, e&&d == u, zero);
36             }
37             else
38             {
39                 tmp[i] = d;
40                 ret += dfs(i - 1, len, e&&d == u, zero);
41             }
42         }
43     }
44     return e ? ret : dp[i][len] = ret;
45 }
46 LL cal(char *s)
47 {
48     int cnt = strlen(s);
49     for (int i = 0; i < cnt; i++) num[cnt-i-1] = s[i] - '0';
50     return dfs(cnt - 1, cnt, 1,1);
51 }
52 int ok(char *s)
53 {
54     int len = strlen(s);
55     for (int i = 0; i < len; i++)
56         if (s[i] != '0'&& s[i] != '8'&&s[i] != '1') return 0;
57     for (int i = 0; i < len / 2; i++)
58         if (s[i] != s[len - i - 1]) return 0;
59     return 1;
60 }
61 int main()
62 {
63     int T;
64     scanf("%d", &T);
65     memset(dp, -1, sizeof(dp));
66     while (T--)
67     {
68         char x[maxn], y[maxn];
69         scanf("%s%s", x, y);
70         printf("%lld\n", cal(y) - cal(x)+ok(x));
71     }
72     
73     return 0;
74 }
View Code

 

posted on 2014-08-04 22:55  仗剑奔走天涯  阅读(362)  评论(0编辑  收藏  举报

导航