1 条题解

  • 0
    @ 2026-10-26 12:26:01

    思路与算法

    用F(x)统计0至x中不含数码4且不含连续62的整数。状态记录位置、上一位是否为6、tight。枚举下一位时跳过4,上一位是6时再跳过2;其余转移更新上一位是否为6。区间答案为F(m)-F(n-1),00作为结束标记不处理。

    正确性说明

    被跳过的数位选择恰好是两类非法情况:当前位4,或上一位6且当前位2。状态完整保留判断新禁串所需的前一位信息,不会把不相邻的6和2误判。合法数与逐位路径一一对应,上界限制准确,故F正确。前导0不会制造4或62,0在区间差中相消,得到所求正号码数量。

    复杂度

    每个前缀查询最多7位,位置乘2种上一位状态,每状态至多10次转移,计算O(L210),另加固定11*2=22槽的缓存初始化。空间O(22+L),包括文本和递归栈。每组两次查询,按00结束,文件总开销与组数线性。

    易错点

    含4一律非法,62必须是相邻顺序;61152合法。不能把出现6或出现2本身当成非法。范围严格小于10^7,最大9999999已测试;00只结束输入。独立答案对全部1至9999999逐个检查十进制文本并建立前缀统计,覆盖完整数值域。

    参考代码(C++11)

    #include <cstring>
    #include <iostream>
    #include <string>
    using namespace std;
    string digits;
    long long memo[11][2];
    long long visit(int pos, bool previousSix, bool tight) {
        if (pos == (int)digits.size())
            return 1;
        if (!tight && memo[pos][previousSix] >= 0)
            return memo[pos][previousSix];
        int upper = tight ? digits[pos] - '0' : 9;
        long long ans = 0;
        for (int d = 0; d <= upper; ++d)
            if (d != 4 && !(previousSix && d == 2))
                ans += visit(pos + 1, d == 6, tight && d == upper);
        if (!tight)
            memo[pos][previousSix] = ans;
        return ans;
    }
    long long count(long long x) {
        if (x < 0)
            return 0;
        digits = to_string(x);
        memset(memo, -1, sizeof(memo));
        return visit(0, false, true);
    }
    int main() {
        long long a, b;
        while (cin >> a >> b) {
            if (a == 0 && b == 0)
                break;
            cout << count(b) - count(a - 1) << '\n';
        }
    }
    
    • 1

    信息

    ID
    1724
    时间
    1000ms
    内存
    512MiB
    难度
    普及
    标签
    递交数
    1
    已通过
    1
    上传者