// cur : 当前位数
// last: 上一位数对应的数字
// status: 可以当作一个bool值
// 比如题目是不要 4和7,那么只要之前的status = false,那么后面不管如何取值都是 false
// limit : 如果能取0~9就为 false, 否则为 true
// 巧妙的地方有两点
// 1.dfs中的dp记忆条件是当前位数的取值是否受限: 因为一个位数受限的值可能不同,对应的dp也会不同,比如
// 一个上限是 8和一个上限是7的dp肯定会不同,对此要想剪枝,只能让dp取不受限的情况下(上限都是9,幸运的是这种情况占多数)
// 如果不受限dp有值就直接返回值,如果受限的话,只能老老实实做dfs求出对应的值;因为有这种记忆方式,才能让dfs不超时
// 2.statue在dfs中的取值要考虑前一次的情况,比如题目不要4和7,如果上一次的status 是true,那么只要当前位数的
// 取值不是4和7就行了,如果是false,那么我后面不管怎么取值都是false; 直到dfs到最后 cur < 1 的时候返回0或者1
// 以此达到一个计数的目的
ll DIG[20], dp[15][15][5];
ll dfs (int cur, int last, int status, bool limit) {
if (cur < 1) return status;
if (!limit && dp[cur][last][status] != -1)
return dp[cur][last][status];
int End = limit ? DIG[cur] : 9;
ll ret = 0;
for (int i = 0; i <= End; ++i)
ret += dfs (cur-1, i, status && (i != 4 && i != 7), limit && (i == End));
if (!limit)
dp[cur][last][status] = ret;
return ret;
}
ll solve (ll x) { // 先把数的位数用DIG数组存起来
int m = 0;
while (x) {
DIG[++m] = x % 10;
x /= 10;
}
return dfs (m, -1, 1, true); // 第一次dfs是最高位的数,所以limit = true, 对应status根据题意定
}