一、题目描述
给定整数区间 \([l,r]\),求该区间内:
- 数位上没有 \(7\)
- 整个数字不是 \(7\) 的倍数
- 所有数位加起来不是\(7\)的倍数
所有符合上面三个条件的数字的 平方和
二、思路分析
这题如果只是求整数区间 \([l,r]\) 内满足要求的数的 个数,难度可以归为 简单
我们先想一下如何求解我说的这个 简单问题
毫无疑问是用 数位DP 来求解,那么在从前向后的搜索过程中,需要向后传递的参数有:
-
前 \(pos\) 位模 \(7\) 的余数 \(val\)
-
前 \(pos\) 位数位上的数字之和模 \(7\) 的余数 \(sum\)
只有前一个位置传递这两个信息,下一个数位才好判断自己怎么办,也就比 前5道数位DP 多了一个参数(对应着多了一个维度,表现在记忆化中就是数组多了一维)罢了
可惜本题要求的不是满足要求的 数字的个数,而是满足要求的 数字平方和
平方和我们没求过,也不知道有啥规律和性质,只好从从前的思考方式去研究一下,看看怎么处理:
先用 数位DP-记忆化搜索 求解问题的方式去思考,如何在求解的过程中 记录/合并信息
既然是 搜索,不妨先用 分治的思想,把 大问题集合 划分成多个 子问题集合,最后再进行 合并
下面我们思考每一位都需要它的后面位给自己向前返回些什么信息呢?
假设 我们当前已经搜出了 \(pos-1\) 层的信息,现在向上 回溯,如何把该信息合并到 大集合 中呢?
从题意要求的结果出发,我们思考一下一个常规形态的位置,怎么样计算出符合条件的数字平方和:考虑在 搜索 时,如何合并多条搜索分支 回溯 的 平方和信息
根据上述 递推式 可知,我们枚举当前数位填 \(a\) 以后下沉 搜索,然后 回溯时 需要传递的 信息 有:
- 能接在 \(a\) 以后的数字 \(A_i\) 的个数 \(\sum_{i}^{}1\)
- 能接在 \(a\) 以后的数字 \(A_i\) 的总和 \(∑_iA_i\)
- 能接在 \(a\) 以后的数字 \(A_i\) 的平方和 \(∑_iA_i^2\)
于是我们可以把 记忆化搜索 的 属性值 开成 3维,分别记录这三个值: \(S0\), \(S1\), \(S2\)
回溯的时候,调用者收到这三个返回值,对这三个值分别进行合并即可
上述递推式给出了 \(s2\) 的合并, \(s1\) 的合并如下:
\(s0\) 的合并就是直接个数相加即可
这题唯一一个 最不起眼的减法操作 在第一步 \(calc(r)−calc(l−1)\)),这里也要 取模
三、实现代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 20;
const int MOD = 1e9 + 7;
LL l, r;
int a[N], al;
int pw[N];
//f记录的是枚举到第pos位,数字模7余数为val,数位之和模7余数为sum
struct Node {
LL s0; //能接在a以后的符合题意数字Ai的个数 %MOD
LL s1; //能接在a以后的符合题意数字Ai的总和 %MOD
LL s2; //能接在a以后的符合题意数字Ai的平方和 %MOD
} f[N][N][N];
//val记录搜索到第pos位上时,当前数字模7的余数
//sum记录搜索到第pos位上时,前几位数位上数字之和模7的余数
//op记录当前搜索是否贴着上界
Node dp(int pos, int val, int sum, int op) {
//递归搜索结束,如果此时符合要求,则计数++
if (!pos) {
// val && sum 不为零,表示两个数字都不是零,符合题意
if (val && sum) return {1, 0, 0};
else return {0, 0, 0};
}
//记忆化搜索
if (!op && ~f[pos][val][sum].s0) return f[pos][val][sum];
Node res = {0, 0, 0};//答案
int up = op ? a[pos] : 9;//搜索上界
//合并信息
for (int i = 0; i <= up; i++) {
if (i != 7) {//题目中要求数位上不能出现7
//dfs到下一层
Node t = dp(pos - 1, (val * 10 + i) % 7, (sum + i) % 7, op && i == a[pos]);
//为了方便看代码,这里用一个中间变量k来存储 a * 10^{p-1}
LL k = (LL) i * pw[pos - 1] % MOD;
//二次幂和的合并(利用t.s2做两次叠加)
t.s2 = (t.s2 + k * k % MOD * t.s0 % MOD) % MOD;
t.s2 = (t.s2 + (LL) 2 * k % MOD * t.s1 % MOD) % MOD;
//一次幂和的合并
t.s1 = (t.s1 + k * t.s0 % MOD) % MOD;
//s0就是个数,累加就可以了
(res.s0 += t.s0) %= MOD, (res.s1 += t.s1) %= MOD, (res.s2 += t.s2) %= MOD;
}
}
return op ? res : f[pos][val][sum] = res;
}
LL calc(LL x) {
memset(f, -1, sizeof f);
al = 0;
while (x) a[++al] = x % 10, x /= 10;
return dp(al, 0, 0, 1).s2;
}
int main() {
//1、预处理10的幂次
pw[0] = 1;
for (int i = 1; i < N; i++) pw[i] = (LL) 10 * pw[i - 1] % MOD;
//2、solve
int T;
cin >> T;
while (T--) {
cin >> l >> r;
//这里一个减法取模要注意
cout << ((calc(r) - calc(l - 1)) % MOD + MOD) % MOD << endl;
}
return 0;
}