AcWing 1083.Windy数(保姆级注释)


分析 :

** 重点:**
1)、利用前缀和思想,求得 l ~ r 的方案数 = dp( r ) - dp( l - 1 )
2)、利用闫氏DP法从集合的角度将所有合法方案不重不漏的考虑到
Ps : (此处笔者采用的就是y总上课讲的方法 : 用树的形状一位一位的考虑清楚)

补充:

f [ i ] [ j ] f[i][j] f[i][j]: 表示的是有i位,且最高位填的是j的方案数
而我们推状态转移方程的时候一般是从不同点出发 :
这里我们从下一位选什么来递推,此时发现只需考虑i - 1位且当前位选k的方案
即 最终的状态转移方程为: f [ i ] [ j ] f[i][j] f[i][j] += f [ i − 1 ] [ k ] f[i - 1][k] f[i1][k] (Ps : 当然需要将边界非法方案给处理掉)

Code + 详细注释 :

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>

#define flr(x,l,r) for (int x = l; x <= r; ++ x)
#define frl(x,r,l) for (int x = r; x >= l; -- x)
#define in(x) scanf("%d", &x)
#define out(x) printf("%d", x)
#define pn printf("\n")
#define vc vector 
#define pb push_back
#define sz size

using namespace std;

const int N = 15;

int f[N][10];

void init() { // 预处理: DP过程
    flr (j, 0, 9) f[1][j] = 1; // 只有一位数的时候都为windy数,即方案数为1 (即边界情况)
    
    flr (i, 2, N) // 枚举位数
        flr (j, 0, 9) // 枚举当前位能取到的数
            flr (k, 0, 9) // 枚举下一位能取到的数
                if (abs(j - k) >= 2) f[i][j] += f[i - 1][k]; // 如果满足条件则加上对应的方案数
}

int dp(int n) { // 数位讨论过程
    if (!n) return 0; // 处理边界
    
    vc<int> nums; // 用vector存每一位的数字
    while (n) nums.pb(n % 10), n /= 10; // 将每一位的数字(这里为10进制)给抠出来放到vector去
                                                    // Ps : 最高位为nums.sz() - 1, 最低位为0
                                                    
    int res = 0, last = -1; // res记录总的方案数、last记录上一位的数
    // 处理无前导零的情况(刚好为nums.sz()位的情况)
    frl (i, nums.sz() - 1, 0) { // 从最高位开始遍历
        int x = nums[i]; // 取出该位上的数字
        
        flr (j, i == nums.sz() - 1, x - 1) // 枚举该位上能取的数字:从0 / 1 到 x - 1 (左侧分支的情况)
                                                /*   1)、 如果该位为最高位,则最少取1
                                                     2)、否则,则最少取0   */
            if (abs(j - last) >= 2)  // 如果满足条件(即相邻两个数字之差至少为2)
                res += f[i + 1][j]; // 则将总方案数加上共有i + 1位(第0位 ~ 第i位)且最高位取j的方案数
       
       // 该位就为x的情况(右侧分支的情况)        
        if (abs(x - last) >= 2) last = x; 
        // 如果该位上的数和上一位的之差至少为2(即说明满足条件),则更新last为x
        else break; // 否则,说明不合法,即不能有继续的分支-->直接跳出
        
        // 最后(最右侧)的情况
        if (!i) res ++ ; 
            // 如果非常好运地来到了最后一位,即说明前面的位都满足条件,只需再加上最右侧的方案数
                // 此时方案数为1,因为每一位的数字都已经确定,相当于就是只有n的本身这一种选择
    }
    
    // 单独处理含有前导零的情况(位数不足nums.sz()位的情况)
    flr (i, 1, nums.sz() - 1) // 枚举位数
        flr (j, 1, 9) // 枚举最高位能取到的数
            res += f[i][j]; // 加上对应的方案数
            
    return res; // 返回总方案数
}

int main() {
    init(); // 预处理
    int l, r;
    
    in(l), in(r); // 输入左右边界
    out(dp(r) - dp(l - 1)); // 输出结果(利用前缀和思想)
    pn; // 输出换行
    
    return 0; // 结束快乐~
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值