【题解】BZOJ 1026 [SCOI2009]windy数

D e s c r i p t i o n

传送门

w i n d y 定义了一种 w i n d y 数。不含前导零且相邻两个数字之差至少为 2 的正整数被称为 w i n d y 数。 w i n d y 想知道, 在 A B 之间,包括 A B ,总共有多少个 w i n d y 数?

S o l u t i o n

首先是套路,将 [ l , r ] 转化为 [ 1 , r ] [ 1 , l 1 ]

反正我觉得我用记忆化搜索解数位DP十分顺溜,这回我就还是用记忆化搜索解这道题。

我们用 d ( i , j ) 表示处理到第 i 位,当前最高位是 j 的方案数。

由于数字的特殊性,在 d f s ( ) 是还要加两个 f l a g 。分别表示是否是最高位(排除前导 0 ),以及当前的数是否和那个上限重合(听起来有点抽象。举个例子就是给定一个数 2430 。当前枚举第三位是 2 的话,第二位就是 1 ~ 9 之间任意一个数,而第三位是 3 的话,第二位只能取 1 ~ 4 )有了状态转移就很方便了。直接把所有可能的都加上即可。

C o d e

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
int d[20][10], bits[20];
int dfs(int pos, int pre, bool limit, bool first) {
    if (pos == 0) return 1;
    if (!limit && !first && d[pos][pre] != -1) return d[pos][pre];
    int u = limit ? bits[pos] : 9, ret = 0;
    for (int i = 0; i <= u; i++) {
        if (first || (!first && abs(pre - i) >= 2))
            ret += dfs(pos - 1, i, limit && i == u, first && !i);
    }
    return limit || first ? ret : d[pos][pre] = ret;
}
int calc(int n) {
    memset(d, -1, sizeof d);
    int len = 0;
    while (n) {
        bits[++len] = n % 10;
        n /= 10;
    }
    return dfs(len, 0, true, true);
}
int main() {
    int a, b;
    scanf("%d%d", &a, &b);
    std::cout << calc(b) - calc(a - 1) << std::endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Diogenes_/article/details/81036335