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[i−1][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; // 结束快乐~
}