题意
思考
写的第一道数位 (dp) 的题,因为感觉最近比较冷门所以一直没学 (QWQ) 我太菜了。
首先,(l) ~ (r) 的 (windy) 数可以转成 (1) ~ (r) 的 (windy) 数减去 (1) ~ (l-1) 的 (windy) 数 (前缀和)
如何求 (1) ~ (k) 的 (windy) 数呢?我是用记忆化搜索实现的,先考虑要用哪些状态来表示:
- 当前搜到第几位数 (pos),(如果超过了位数就返回)
- 由于题中的 (windy) 数相邻两位之间有关系,那么我们需要记录上一位数是什么,才能判断要不要统计答案,所以还要记录该位的上一位 (pre)
- 由于有最大值,如果前一位已经被限制了,那么下一位的最大值也有限制,例如:求 (1) ~ (19260817) 的满足条件的数的个数,前面已经搜到了 (19******),第三位就只能枚举到 (2) 而不是 (9)。
- 由于位数是不定的,但我们还是要从最高位开始搜,所以会有前导零,我们还要记录该位前一位是否有前导零(即继续判断该位是否是最高位/前导零)。
状态表示已经清楚,下面就开始转移了:
首先限制枚举的最大值
ll MAX = limit ? a[pos] : 9;//limit(0/1)表示是否有限制,a[pos]是pos位上的最大值
接下来枚举该位为 (0) ~ (MAX)
for(ll i=0; i<=MAX; i++){
if(!lead && abs(i - pre) < 2) continue;
if(!i && lead) ans += dfs(pos-1, 0, 1, i==MAX&&limit);
else if(i && lead) ans += dfs(pos-1, i, 0, i==MAX&&limit);
else ans += dfs(pos-1, i, 0, i==MAX&&limit);
}
- 如果不是第一位并且不满足条件,不统计答案
- 如果当前位选择 (0) 并且有前导零,那么该位也是前导零,统计答案
- 如果该位不是零但有前导零,那么该位为最高位,统计答案
- 其他状况,满足条件,统计答案
至此,我们就能 (AC) 本题了~
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[20][20], len, a[20];
ll dfs(ll pos, ll pre, ll lead, ll limit){
if(pos == 0) return 1;
if(!lead && !limit && dp[pos][pre] != -1) return dp[pos][pre];
ll ans = 0;
ll MAX = limit ? a[pos] : 9;
for(ll i=0; i<=MAX; i++){
if(!lead && abs(i - pre) < 2) continue;
if(!i && lead) ans += dfs(pos-1, 0, 1, i==MAX&&limit);
else if(i && lead) ans += dfs(pos-1, i, 0, i==MAX&&limit);
else ans += dfs(pos-1, i, 0, i==MAX&&limit);
}
if(!lead && !limit) dp[pos][pre] = ans;
return ans;
}
ll part(ll x){
memset(dp, -1, sizeof(dp));
ll len = 0;
while(x){
a[++len] = x % 10; x /= 10;
}
return dfs(len, 0, 1, 1);
}
ll x, y;
int main(){
cin >> x >> y;
cout << part(y) - part(x-1);
return 0;
}
总结
注意我们是在没有限制最大数的情况下才记忆化:由于有限制条件时,满足条件的数肯定会比无限制的时候少(就是他们的个数不相等),所以无法记忆化