• HDU 2089 不要62 数位dp入门


    http://acm.hdu.edu.cn/showproblem.php?pid=2089

    dp[i][j]表示长度为i的数字中,开头的数字是j的时候,有多少种合法的情况。

    预处理挺好理解,关键是那个统计,比较难。

    比如我统计的是数字: 456

    那么,从高到低枚举。先枚举第一位,4、再枚举5、....

    那么,< 4 的,长度是3位的合法数字,都应该算做贡献。就是ans += dp[3][0...3]

    dp[3][1]好理解,就是100、110、....199那些。

    dp[3][2]那些同理,不处理到dp[3][4]也好理解,因为dp[3][4]包含了499那些,就是超越范围了。

    然后,dp[3][0]是什么呢?其实就是0xx,其中,xx就是00---99

    所以也就是所有的1位数和2位数的合法情况。(这个回归下dp预处理就知道了)

    而后来的枚举下一位的5,dp[2][0...4]是同理的,但是其是和第一位的4结合的,也就是dp[2][0]包括了所有1位数的合法情况,然后组合成40x。

    所以后面的枚举其实这是为了和上一位组合成合法情况。所以当其中某些数字破坏了条件,例如出现了4,或者已经出现了62,就要提前break

    还有需要注意的是处理不到n这个数字的,因为都是枚举每一位的-1那个大小,所以只需要把他们 + 1即可。

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <assert.h>
    #define IOS ios::sync_with_stdio(false)
    using namespace std;
    #define inf (0x3f3f3f3f)
    typedef long long int LL;
    
    
    #include <iostream>
    #include <sstream>
    #include <vector>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <bitset>
    int dp[10][20];
    void init() {
        dp[0][0] = 1;
        for (int i = 1; i <= 7; ++i) {
            for (int j = 0; j <= 9; ++j) {
                if (j == 4) continue;
                for (int k = 0; k <= 9; ++k) {
                    if (k == 4 || j == 6 && k == 2) continue;
                    dp[i][j] += dp[i - 1][k];
                }
            }
        }
    }
    char str[222];
    int calc(int val) {
        int lenstr = 0;
        while (val / 10 > 0) {
            str[++lenstr] = val % 10 + '0';
            val /= 10;
        }
        str[++lenstr] = val + '0';
        str[lenstr + 1] = '';
        int ans = 0;
        for (int i = lenstr; i >= 1; --i) {
            for (int j = 0; j <= str[i] - '0' - 1; ++j) {
                if (j == 2 && str[i + 1] == '6') continue;
    //            if (j == 4) continue;
                ans += dp[i][j];
            }
            //后来枚举的,都是和前面的哪一位结合
            //比如456,就是4XX,然后45X
            if (str[i] == '4') break;
            if (str[i] == '2' && str[i + 1] == '6') break;
        }
    //    cout << ans << endl;
        return ans;
    }
    int n, m;
    void work() {
    //    cout << dp[5][4] << endl;
        cout << calc(m + 1) - calc(n) << endl;
    }
    int main() {
    #ifdef local
        freopen("data.txt", "r", stdin);
    //    freopen("data.txt", "w", stdout);
    #endif
        init();
        while (scanf("%d%d", &n, &m) != EOF && (n + m)) work();
        return 0;
    }
    View Code

    复习了一下,下面是新的总结

    HDU 2089 不要62
    int dp[10][20]; // dp[i][j]表示长度是i的数字中,以j开头的合法情况
    void init() {
        dp[0][0] = 1;
        for (int i = 1; i <= 7; ++i) { //枚举数字的长度是多少位
            for (int j = 0; j <= 9; ++j) { //枚举长度是i的开头数字
                if (j == 4) continue; //4是不合法情况,跳过了,所以dp[i][4] = 0;
                for (int k = 0; k <= 9; ++k) { //枚举长度是i - 1的开头数字
                    if (k == 4 || j == 6 && k == 2) continue; //其实这个k = 4不跳过也一样,= 0
                    dp[i][j] += dp[i - 1][k];
                }
            }
        }
        //dp[i][0] += dp[i - 1][0] + dp[i - 1][1] ... + dp[i - 1][9];
    }
    注意的是dp[i][0]的意义,dp[i][0]表示长度是i的,以0开头的合法数字。也就是0XX的形式,所以是包含了000---099,也就是所有合法的1位数和2位数之和。那000是什么?不管了,这个是多余的,也可以看作是合法的一位数,虽然题目的区间不包含000,但是R – L的时候会同时抵消这个多余的计数。
    比如我统计的是数字: 456
    那么,从高到低枚举。先枚举第一位,4、再枚举5、....那么,< 4 的,长度是3位的合法数字,都应该算做贡献。就是ans += dp[3][0...3],dp[3][1]好理解,就是100、110、....199那些。
    dp[3][2]那些同理,不处理到dp[3][4]也好理解,因为dp[3][4]包含了499那些,就是超越范围了。而后来的枚举下一位的5,dp[2][0...4]是同理的,但是其是和第一位的4结合的,也就是dp[2][0]包括了所有1位数的合法情况,然后组合成40X。所以后面的枚举其实这是为了和上一位组合成合法情况。所以当其中某些数字破坏了条件,例如出现了4,或者已经出现了62,就要提前break。还有需要注意的是处理不到n这个数字的,因为都是枚举每一位的-1那个大小,所以只需要把他们 + 1即可。
    int calc(int val) { //calc(R + 1) - calc(L + 1 - 1)
        int lenstr = 0;
        while (val / 10 > 0) {
            str[++lenstr] = val % 10 + '0';
            val /= 10;
        }
        str[++lenstr] = val + '0';
        str[lenstr + 1] = '';
        int ans = 0;
        for (int i = lenstr; i >= 1; --i) {
            for (int j = 0; j <= str[i] - '0' - 1; ++j) {
                if (j == 2 && str[i + 1] == '6') continue; //62X是不合法的,要跳过
                //那么j = 4呢?不跳过?其实是一样的,因为预处理的时候dp[i]][4] = 0的。
                ans += dp[i][j];
            }
            //后来枚举的,都是和前面的哪一位结合,比如456,就是4XX,然后45X
            if (str[i] == '4') break; //4XX,下一次枚举就不合法了
            if (str[i] == '2' && str[i + 1] == '6') break; 
        }
        return ans;
    }
    View Code
  • 相关阅读:
    UIlabel自适应高度和自动换行
    ios2048小游戏
    NSPredicate
    NSURLConnection同步与异步请求 问题
    视频播放器开发中遇到的一些小问题MPMoviePlayerController
    storyboard中xib文件不加载问题
    cell的imageVIew的fram问题
    NSArray和NSDictionary添加空对象,以及nil和Nil和NULL和NSNull
    xcode5 和code6中push后方法执行的先后问题
    UItableView自定义标题(headerView)重用问题
  • 原文地址:https://www.cnblogs.com/liuweimingcprogram/p/6298085.html
Copyright © 2020-2023  润新知