HDOJ 6148

题目

给一个整数$N$,问有多少个不超过$N$的正整数满足性质$P$。
$a$满足性质$P$当且仅当$a$的十进制从左往右各个数位上的数没有先严格递增再严格递减的情况。

数据范围

$0\leq length(N) \leq 100$

做法

数位DP。

定义状态为:(int pos, int pren, int s, bool zero, bool limit)表示当前处理到第pos位,前一位的数字是pren,s的值为0或1,表示之前是否递增过了,zero表示之前是否全是0(即前导零标记),limit表示这一位的取值范围是否受限。

为什么要用前导零标记呢?因为不标记的话,会对s的判断产生影响。例如:当前处理的数字是4位的。现在处理到前2位是0的情况。在枚举第3位的时候,对于001x这样的2位数,会错误地认为它从0到1递增了,但其实这个2位数1x在1这里并没有递增。

若额外用一位`bool zero来判断前导零的话,这一维也要放到记忆化数组里,否则就错了。

当然,这一题也可以将前导零的判断归到pren上去,这样记忆话数组就可以少一维。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 100 + 5;
const int MOD = 1000000007;
int digits[MAX_N], len;
int mem[MAX_N][11][2][2];
int dfs(int l, int pn, int s, bool zero, bool limit)
{
if (l == -1) {
return 1;
}
if (!limit && mem[l][pn][s][zero] != -1) {
return mem[l][pn][s][zero];
}
int ans = 0;
int maxi = limit ? digits[l] : 9;
for (int i = 0; i <= maxi; ++i) {
int newS = s;
if (s == 1) {
if (i < pn) continue;
} else {
if (i > pn && !zero) newS = 1;
}
ans += dfs(l - 1, i, newS, zero && i == 0, limit && i == maxi);
if (ans >= MOD) ans -= MOD;
}
if (!limit) mem[l][pn][s][zero] = ans;
return ans;
}
char s[MAX_N];
int main()
{
memset(mem, -1, sizeof mem);
int T; scanf("%d", &T);
while (T--) {
scanf("%s", s);
len = strlen(s);
for (int i = 0; i < len; ++i) {
digits[len - 1 - i] = s[i] - '0';
}
int ans = dfs(len - 1, 11, 0, true, true) - 1;
if (ans < 0) ans += MOD;
printf("%d\n", ans);
}
return 0;
}