数位DP统计->手机号码(洛谷4124)

https://www.luogu.com.cn/problem/P4124

题意:给区间[l, r], 求满足条件的手机号。条件:4,8不同时出现,有AAA数字出现,没有前导0。

分析:没有前导0,如果[1,x],x < 1e11, 直接return 0。 前缀差分,高位到低位,状态是前两位数字跟当前的4,8,连续状态。 数位dp。

/*

    这么设状态,是因为假如我们的pos一共有11位,现在在第7位。虽然第pos = 8, pre = a, sec_pre =b已经被计算过了,但是第11位是什么,我们不知道,
    我们必须定义4出现跟8出现的情况才能确保第11位的数字出现,跟我们之前计算过的状态不冲突(也就是说之前计算的状态是可用的),如果之前计算的时候第11位是4,
    但是现在第11位是2,那么之前计算的状态就失效了。 动态规划的核心是历史计算结果避免重复计算,但是我们现在要利用的结果如果不能保证跟历史计算的结果 状态完全相同,
    那么这个结果就不能利用了。


*/

void solve(){
    long long a, b;
    cin >> a >> b;

    long long dp[20][20][20][2][2][2];
    vector<int> num;

    function<long long(int, int, int, bool, bool, bool, bool, bool)> dfs = [&](int pos, int pre, int sec_pre, bool lead, bool limit, bool has8, bool has4, bool con)->long long{
       // cout << pos << " " << pre << "  " << sec_pre << endl;
        if (has8 && has4){
            return 0;
        }
        if (pos == 0){
            return con;
        }
        if (limit == false && dp[pos][pre][sec_pre][con][has8][has4] != -1){
            return dp[pos][pre][sec_pre][con][has8][has4];
        }
        long long res = 0;
        int up = (limit == true ? num[pos] : 9);
        for (int i = 0; i <= up; ++i){
            if (lead == true && i == 0){
                continue;
            }
            res += dfs(pos - 1, i, pre, false, limit && (i == up), has8 || (i == 8), has4 || (i == 4), con || (i == pre && i == sec_pre));
        }
        if (!limit){
            dp[pos][pre][sec_pre][con][has8][has4] = res;
        }
        return res;
    };

    auto cal = [&](long long x)->long long{
        num.resize(1);
        while (x){
            num.emplace_back(x % 10);
            x /= 10;
        }
        memset(dp, -1, sizeof(dp));
        if (int(num.size()) != 12){
            return 0;
        }
        return dfs(int(num.size()) - 1, 0, 0, true, true, false, false, false);
    };

    cout << cal(b) - cal(a - 1) << '\n';
}
posted @ 2024-01-15 22:09  _Yxc  阅读(26)  评论(0)    收藏  举报