洛谷 P2657 windy 数(数位DP)

题意:如果在一个不含前导0的数字中,他的相邻两个数字的差不超过2那么我们称之为一个windy数,现在需要求解$$​区间内有多少个windy数

解题思路:对每一位上的数字进行预处理可以得当前最高位数字所对应的值,之后依序考虑每一位上的数字是否可一存放当前数字即可,具体解法为:对于比当前位置要小的数字,可以将其所对应的结果全部加上,对于和当前位数一样的数字,我们需要枚举首位数字,来判断是可以直接加入还是需要验证是否可以加入,当找到一个不可加入的数字后,退出。

解题代码:

#include <iostream>
#define int long long
using namespace std;
const int mod = 1e6 + 7;
const int maxn = 1e2 + 20;
int a[maxn];
int dp[maxn][maxn];
int func(int x){
    int len = 0;
    while(x > 0ll)a[++len] = x % 10,x /= 10;
    int sum = 0ll;
    //对于位数比当前位数要小的数,那么直接将答案加入即可
    for(int i = 1;i <= len - 1;i++){
        for(int j = 1;j <= 9;j++){
            sum += dp[i][j];
        }
    }
    //对于首位数字比给定数字要小的数,我们也是直接将答案加入
    for(int i = 1;i < a[len];i++)sum += dp[len][i];
    //对于剩下的数要去判断方案的合法性
    //此处枚举位数
    for(int i = len - 1;i>=1;i--){
        //去找每一位前面最多能放得
        for(int j = 0;j <= a[i] - 1;j++){
            if(abs(j - a[i + 1]) >= 2)sum += dp[i][j];
        }
        //如果当前位置和上一位的差值已经比二要小了,那么之后不管怎么摆都是不合法的,可以直接跳出。
        if(abs(a[i + 1] - a[i]) < 2)break;
    }
    return sum;
}
signed main(){
    //对于只有一位数的情况,至少为一
    for(int i = 0;i <= 9;i++)dp[1][i] = 1ll;
    //此处枚举的是位数
    for(int i = 2;i <= 10;i++){
        //此处枚举的是首位数字,因为数字中的单独一部分可能有前导零,所以此处要从0开始
        for(int j = 0;j <= 9;j++){
            //此处枚举的是次位的数字,如果次位数字和首位数字相差不超过二
            //那么可以将次位数字为k的方案加入到当前方案中
            for(int k = 0;k <= 9;k++){
                if(abs(k-j) >= 2)dp[i][j] += dp[i - 1][k];
            }
        }
    }
    int n,m;
    cin >> n >> m;
    if(n > m)swap(n, m);
    printf("%lld\n",func(m + 1) - func(n));
    system("pause");
    return 0;
}
posted @ 2021-08-01 22:06  0xYuk1  阅读(50)  评论(0)    收藏  举报