Acwing1086

1. 问题分析

我们需要统计在一个区间 [l, r] 内所有满足以下条件的数的平方和之和:

  • 该数的每一位数字不能为 7
  • 该数的数位之和模 7 不为 0
  • 该数自身模 7 的余数不为 0

具体来说,要求得出符合条件的数字的平方和的和,即在 [l, r] 范围内所有符合条件的数字的平方和累加值。

2. 算法设计与实现

使用 数位动态规划 方法解决这个问题。数位 DP 的主要思想是逐位处理数,并根据特定条件进行状态转移。

关键变量与结构
  • a[N]:用于存储数 x 的每一位,便于按位递归。
  • f[N][N][N]:定义的三维 DP 数组,用于存储中间结果,避免重复计算。其中:
    • f[pos][val][sum] 表示从高到低枚举到第 pos 位时,当前数模 7 的余数为 val,数位和模 7 的余数为 sum 的状态。
    • 其中 F 结构体 f[pos][val][sum] 包含了三个变量:s0s1s2,分别表示符合条件的数字个数、这些数字的一次幂和、平方和。
  • pow10[N]:存储 10 的幂次,以计算每一位对当前数值的实际贡献。
结构体 F
struct F
{
    LL s0, s1, s2;
    F(): s0(0ll), s1(0ll), s2(0ll){}; //无参构造函数
    F(LL _0, LL _1, LL _2): s0(_0), s1(_1), s2(_2){}; //含参构造函数
    void operator += (const F& t) //重载+=,用于信息合并
    {
        s2 = (s2 + t.s2) % MOD;
        s1 = (s1 + t.s1) % MOD;
        s0 = (s0 + t.s0) % MOD;
    }
} f[N][N][N];
  • s0:符合条件的数字个数。
  • s1:符合条件的数字的一次幂和(位数和)。
  • s2:符合条件的数字的平方和。
核心递归函数 dp

dp(pos, val, sum, op) 通过递归的方式从高位到低位进行搜索,并判断当前数是否符合条件。

  1. 递归终止条件

    if (!pos)
    {
        if (val && sum) return {1, 0, 0};
        else return {0, 0, 0};
    }
    

    pos == 0 时(即递归到底,所有位处理完成),如果满足 val != 0sum != 0(即当前数满足题目条件),返回 {1, 0, 0} 表示找到一个符合条件的数。

  2. 记忆化处理

    if (!op && ~f[pos][val][sum].s0) return f[pos][val][sum];
    

    如果 op == 0(不再严格对齐上界)且 f[pos][val][sum] 已计算过,则直接返回缓存值,避免重复计算。

  3. 状态转移与结果合并
    递归处理每一位数字,更新符合条件的数字的计数、一次幂和和二次幂和。具体步骤:

    • 遍历每一位数字 i,且 i != 7(因为题目要求数位上不能包含 7)。
    • 递归计算下一位的结果 t
    • 使用 k 表示当前位对数值的实际贡献:k = i * 10^(pos-1) % MOD

    然后更新 s1s2

    t.s2 = (t.s2 + 2ll * k % MOD * t.s1 % MOD) % MOD;
    t.s2 = (t.s2 + k * k % MOD * t.s0 % MOD) % MOD;
    t.s1 = (t.s1 + k * t.s0 % MOD) % MOD;
    res += t;
    
    • s2 更新:使用 2 * k * s1k^2 * s0 更新平方和。
    • s1 更新:直接将当前位贡献 k 加到一次幂和 s1 中。

    一次幂和 s1 和平方和 s2 的推导

    假设当前我们在构造一个数,通过给它增加一个数字 i 来构成新的数,这个数在原有数的基础上增加了一位,并且这位的实际贡献是 k = i * 10^{pos-1} % MOD。这里 pos 表示当前位的位置,10^{pos-1} 是该位置上的位权(即这位数字对整体数值的影响),MOD 是取模操作来保证计算不溢出。

    t.s1 的更新:一次幂和

    首先考虑一次幂和的更新(即新的符合条件的数字的位数和)。

    1. 假设 t.s0 表示符合条件的数字个数。
    2. 每个符合条件的数字会增加 k 的贡献到位数和中。

    因此:

    t . s 1 = ( t . s 1 + k × t . s 0 ) m o d    M O D t.s1 = (t.s1 + k \times t.s0) \mod MOD t.s1=(t.s1+k×t.s0)modMOD

    • 这里 k * t.s0 表示当前位 k 对所有符合条件数字的位数和的贡献。
    • 累加上原来的 t.s1,得到当前的位数和。
    t.s2 的更新:平方和

    平方和 t.s2 的更新更为复杂,因为我们需要考虑新加入的位对平方和的影响,这涉及到一次幂和 s1 和当前位的平方贡献。

    平方和的更新公式可以通过以下步骤推导:

    1. t.s1 是已经累加的位数和(一次幂和)。
    2. 新加入的位 k 对所有符合条件数字的平方和产生影响。

    平方和的更新公式可以分解成两个部分:

    1. 一次幂和对平方和的影响:根据二次展开公式 (a + b)^2 = a^2 + 2ab + b^2,当我们给数字增加一个位 k 时,平方和中会增加 2 * k * s1 的项。
    2. 新位的平方贡献:即 k^2 * s0,因为当前位数 k 本身也会对平方和产生贡献。

    因此,完整的平方和更新公式为:

    t . s 2 = ( t . s 2 + 2 × k × t . s 1 + k 2 × t . s 0 ) m o d    M O D t.s2 = (t.s2 + 2 \times k \times t.s1 + k^2 \times t.s0) \mod MOD t.s2=(t.s2+2×k×t.s1+k2×t.s0)modMOD


    实际代码的推导与解释

    代码中每一行的具体含义如下:

    t.s2 = (t.s2 + 2ll * k % MOD * t.s1 % MOD) % MOD;
    
    • 2ll * k % MOD * t.s1 % MOD:表示 2 * k * t.s1,即新位 k 对平方和的影响。这部分来源于 (a + b)^2 = a^2 + 2ab + b^2 的展开式中的 2ab 部分。
    t.s2 = (t.s2 + k * k % MOD * t.s0 % MOD) % MOD;
    
    • k * k % MOD * t.s0 % MOD:表示 k^2 * t.s0,即新位 k 自身的平方对平方和的贡献。这部分来源于 (a + b)^2 展开式中的 b^2 项。
    t.s1 = (t.s1 + k * t.s0 % MOD) % MOD;
    
    • k * t.s0 % MOD:表示新位 k 对一次幂和的贡献。
  4. 返回与缓存

    return op ? res : f[pos][val][sum] = res;
    

    如果当前受上界限制(op == 1),不缓存,否则将结果 res 存入 f 数组以便复用。

辅助函数 calc
LL calc(LL x)
{
    memset(f, -1, sizeof f); al = 0;
    for ( ; x; x /= 10) a[ ++ al] = x % 10;
    return dp(al, 0, 0, 1).s2;
}

calc(x) 用来计算从 1x 的符合条件数字的平方和。通过 dp 递归获取最终的 s2 值。

主函数 main
int main()
{
    pow10[0] = 1;
    for (int i = 1; i < 20; i++) pow10[i] = 10ll * pow10[i - 1] % MOD;

    cin >> T;
    while (T --)
    {
        cin >> l >> r;
        cout << ((calc(r) - calc(l - 1)) % MOD + MOD) % MOD << endl;
    }
    return 0;
}

main 函数中:

  1. 初始化 pow10 数组,用于快速计算位权。
  2. 对每组区间 [l, r],通过 calc(r) - calc(l - 1) 获取 [l, r] 区间符合条件数字的平方和之和。结果取模处理防止负值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值