• Luogu P2657 [SCOI2009]windy数


    传送门

    终于明白数位DP是什么了

    虽然说是dp,实际上是记忆化搜索

    看这道题的题面:

    不含前导零,且相邻两个数字之差至少为2的正整数被称为windy数。

    求:在A和B之间,包括A和B,总共有多少个windy数?

    这道题中给出了几个限定条件。

    其中,前导零、数字上限(A,B)是数位dp的题目中比较常见的。

    而“相邻数字之差至少为2”则是这道题特有的限定条件。

    考虑用记忆化搜索来解决这道题。

    首先考虑普通的搜索。搜索两次,边界分别设为给定的两个数,那么(solve[b] - solve[A-1])即为答案。

    假设已经处理出了边界条件,共有n个数位,且从小到大第i位上的数字是num[i]。

    那么,搜索时需要传递哪些变量?

    int dfs(int cur,int las,bool start,bool lim) {
        if(!cur) return 1;
        if(f[cur][las] && !start && !lim) return f[cur][las];
        int ans = 0;
        for(int i = 0; i <= (lim?num[cur]:9); i++) {
            if(!start && abs(i-las)<2) continue;
            ans += dfs(cur-1,i,(start && !i),(lim && i==num[cur]));
        }
        if(!start && !lim) f[cur][las] = ans;
        return ans;
    }

    cur表示当前是第几位(初始为n)

    • 当cur减为0时,返回1

    las表示上一位的数

    start表示之前的所有数位是否为前导零

    • 如果是前导零,则对后面的数位不造成影响(即使差值不大于2也可以)
    • 当且仅当上一位是前导零(start为true),且这一位是0时,start为true
    • start初始为true

    lim表示之前的所有数位是否达到边界

    • 如果到达边界,则枚举这一位时只枚举到这一位的实际大小。比如,数字为1234,前两位恰为12时,第三位只能枚举0~3,否则枚举0~9
    • 当且仅当上一位到达边界(lim为true),且这一位的数字和实际数字相同时,lim为true。
    • lim初始为true

    普通的dfs完成了,考虑记忆化搜索。

    需要解决的问题是:什么样的答案是可以记下来的?

    能够直接调用的答案必须是有普遍性的。

    可以想到,如果确定了当前位数和上一位位数,答案就是固定的。

    不过,这也存在特殊情况。如果start或者lim为true,容易举出反例。

    那么,排除掉这两种情况,其余答案记录,即为正解。

    代码如下

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #include<cstring>
    #include<queue>
    #define MogeKo qwq
    using namespace std;
    int A,B,n,f[20][20],num[20];
    
    
    int dfs(int cur,int las,bool start,bool lim) {
        if(!cur) return 1;
        if(f[cur][las] && !start && !lim) return f[cur][las];
        int ans = 0;
        for(int i = 0; i <= (lim?num[cur]:9); i++) {
            if(!start && abs(i-las)<2) continue;
            ans += dfs(cur-1,i,(start && !i),(lim && i==num[cur]));
        }
        if(!start && !lim) f[cur][las] = ans;
        return ans;
    }
    
    int solve(int x) {
        memset(f,0,sizeof(f));
        n = 0;
        while(x) {
            num[++n] = x%10;
            x /= 10;
        }
        return dfs(n,0,1,1);
    }
    
    int main() {
        scanf("%d%d",&A,&B);
        printf("%d",solve(B)-solve(A-1));
        return 0;
    }
    View Code
  • 相关阅读:
    设计模式之工厂模式
    面向对象的五大原则
    抽象类和接口、类库
    静态
    面向对象三大特性
    JVM(Java虚拟机)优化大全和案例实战
    Java调用Lua脚本(LuaJava使用、安装及Linux安装编译)
    Java调用.dll文件
    linux yum命令详解
    linux nohup命令
  • 原文地址:https://www.cnblogs.com/mogeko/p/11249753.html
Copyright © 2020-2023  润新知