[SCOI2009]windy数

题意

Here

思考

写的第一道数位 \(dp\) 的题,因为感觉最近比较冷门所以一直没学 \(QWQ\) 我太菜了。

首先,\(l\) ~ \(r\)\(windy\) 数可以转成 \(1\) ~ \(r\)\(windy\) 数减去 \(1\) ~ \(l-1\)\(windy\) 数 (前缀和)

如何求 \(1\) ~ \(k\)\(windy\) 数呢?我是用记忆化搜索实现的,先考虑要用哪些状态来表示:

  1. 当前搜到第几位数 \(pos\),(如果超过了位数就返回)
  2. 由于题中的 \(windy\) 数相邻两位之间有关系,那么我们需要记录上一位数是什么,才能判断要不要统计答案,所以还要记录该位的上一位 \(pre\)
  3. 由于有最大值,如果前一位已经被限制了,那么下一位的最大值也有限制,例如:求 \(1\) ~ \(19260817\) 的满足条件的数的个数,前面已经搜到了 \(19******\),第三位就只能枚举到 \(2\) 而不是 \(9\)
  4. 由于位数是不定的,但我们还是要从最高位开始搜,所以会有前导零,我们还要记录该位前一位是否有前导零(即继续判断该位是否是最高位/前导零)。

状态表示已经清楚,下面就开始转移了:

首先限制枚举的最大值

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);
}
  1. 如果不是第一位并且不满足条件,不统计答案
  2. 如果当前位选择 \(0\) 并且有前导零,那么该位也是前导零,统计答案
  3. 如果该位不是零但有前导零,那么该位为最高位,统计答案
  4. 其他状况,满足条件,统计答案

至此,我们就能 \(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;
}

总结

注意我们是在没有限制最大数的情况下才记忆化:由于有限制条件时,满足条件的数肯定会比无限制的时候少(就是他们的个数不相等),所以无法记忆化

posted @ 2018-11-08 08:58  alecli  阅读(124)  评论(0编辑  收藏  举报