[BZOJ]1026[SCOI2009]windy数
题目链接:[SCOI2009]windy数
题意:
求[l,r]之间的有多少个数满足:不包含前导0,且相邻两位数字差大于等于2。
题解:
第一次不看题解一遍通过数位dp祭(虽然以前做过这道题,但是已经忘了)。
数位dp一直是恶心我的难点,这道题刚好是一道很简单的的数位dp,于是想尝试一下。
数位dp肯定要记忆化搜索,其他的奇怪实现方法我觉得都没有记搜简单明了且方便实现。
dfs(i, f, lst, limit)
表示第i位,是否有前导0,前面一个数是多少,是否碰到上限的方案数。
于是枚举当前位置的数,需要考虑以下几种情况。
首先当现在枚举的数是0的时候,会出现传递前面前导0的作用,于是判断前导0。
然后如果当前顶到上界,需要考虑当前数字是否小于上界。
如果没顶到上界,则需要考虑当前数字跟上一位差值是多少。
记住每种情况都要分类是否前面为前导0,因为前导0跟第一个数字不用比较差值大于等于2。
#include <bits/stdc++.h> #define Mid ((l + r) / 2) #define lson (rt << 1) #define rson (rt << 1 | 1) using namespace std; int read() { char c; int num, f = 1; while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; return f * num; } int dp[20][2][20][2]; int a[1009], cnt; int dfs(int res, int f, int lst, int limit) { if(dp[res][f][lst][limit] != -1) return dp[res][f][lst][limit]; dp[res][f][lst][limit] = 0; if(res == 0) return dp[res][f][lst][limit] = 1; for(int i = 0; i < 10; i++) { if(i == 0) { if(f == 1) dp[res][f][lst][limit] += dfs(res - 1, 1, 0, 0); else if(lst >= 2) dp[res][f][lst][limit] += dfs(res - 1, 0, 0, limit && (i == a[res])); } else if(limit && i <= a[res]) { if(f == 1) dp[res][f][lst][limit] += dfs(res - 1, 0, i, limit && (i == a[res])); else if(abs(i - lst) >= 2) dp[res][f][lst][limit] += dfs(res - 1, 0, i, limit && (i == a[res])); } else if(!limit) { if(f == 1 || abs(i - lst) >= 2) dp[res][f][lst][limit] += dfs(res - 1, 0, i, 0); } } return dp[res][f][lst][limit]; } signed main() { int l = read() - 1, r = read(); int cntl, cntr; cnt = 0; while(r > 0) a[++cnt] = r % 10, r /= 10; memset(dp, -1, sizeof(dp)); cntr = dfs(cnt, 1, 0, 1); cnt = 0; while(l > 0) a[++cnt] = l % 10, l /= 10; memset(dp, -1, sizeof(dp)); cntl = dfs(cnt, 1, 0, 1); printf("%d\n", cntr - cntl); return 0; }