JZOJ 4239. 【五校联考5day2】光棍 (Standard IO)
题意
满足如下条件中任意一个的数\(x\)是合法的:
- \(x\)是\(a\)的倍数。
- \(x\)在十进制下的数位和是\(a\)的倍数。
- \(x\)在十进制下某个数位是\(a\)。
其中\(a\)给定,\(2\leq a \leq 9\)。
现在给出\(l,r,a\),对于\([l,r]\)中每个不合法的\(x\),求\(\sum x^2\)。
\(1\leq l \leq r \leq 10^{18},\ 2\leq a \leq 9\)
Solution
这显然是数位dp对吧。。。
但是呢我不会数位dp,至今还没写对过一题,于是今晚恶补了一下,发现也不是很难,套个记忆化搜索的模板就行了。
首先可以转换一下问题,用\([1,r]\)的答案减掉\([1,l-1]\)的答案。
但是题目要求统计不合法数的平方和,我们转换为求合法数的平方和,然后用所有数的平方和减去它(这个可以套公式\(O(1)\)求)。
dfs
的参数是下面几个:
note dfs(int len, int zero, int flag, int mo1, int mo2, int app)
{
}
其中len表示是从低到高第几位,zero表示是否有前导零,flag表示前面几位是不是顶着上界填的,mo1表示当前数对a取模的值,mo2表示当前数的数位和对a取模的值,app(appear)是个0/1状态,表示当前数数位里是否出现了a。
先考虑满足条件的数的个数怎么求,显然套个模板就行了。
再考虑满足条件的数的和怎么求,设后面的数位中某个满足条件的数为\(b\),现在你要填\(a\),后面还有\(k\)位待填,那么填完以后这个数就是\(a*10^k+b\),那么我们只需要知道后面的数位中:
1.满足条件的数的个数
2.满足条件的数的和
就能够求出当前状态满足条件的数的和。
再考虑满足条件的数的平方和怎么求,还是用上面的\(a,b,k\),那么\((a*10^k+b)^2=a^2*10^{2k}+2*a*b*10^k+b^2\)。
那也就是知道后面的数位中:
1.满足条件的数的个数
2.满足条件的数的和
3.满足条件的数的平方和
就能够求出当前状态满足条件的数的平方和。
然后这题就解决了。
Code
那个\(166666668\)是\(6\)的逆元,因为要做除法。还有\(n\)原本是\(10^{18}\)级别的,如果不先模\(P\)就会爆long long
,某gsm因此正解fst了。
#include <cstdio>
#include <cstring>
typedef long long ll;
const ll P = 1e9 + 7;
int T, a, len, arr[20];
ll l, r, pow[40];
struct note { ll s1, s2, s3; } f[20][10][10][2][2];
ll calc(ll n) { n %= P; return 166666668ll * n % P * (n + 1) % P * (2 * n + 1) % P; }
note dfs(int len, int zero, int flag, int mo1, int mo2, int app)
{
if (!len) return (note){(!mo1 || !mo2 || app), 0, 0};
if (!flag && ~f[len][mo1][mo2][app][zero].s1) return f[len][mo1][mo2][app][zero];
note ret = (note){0, 0, 0};
int mx = flag ? arr[len] : 9;
for (int i = 0; i <= mx; i++)
{
note tmp = dfs(len - 1, zero && !i, flag && i == arr[len], (mo1 * 10 + i) % a, (mo2 + i) % a, app || i == a);
ret.s1 = (ret.s1 + tmp.s1) % P;
ret.s2 = (ret.s2 + i * pow[len - 1] % P * tmp.s1 % P + tmp.s2) % P;
ret.s3 = (ret.s3 + i * i % P * pow[2 * (len - 1)] % P * tmp.s1 + i * pow[len - 1] % P * 2 % P * tmp.s2 % P + tmp.s3) % P;
}
if (!flag) f[len][mo1][mo2][app][zero] = ret;
return ret;
}
ll solve(ll n)
{
memset(f, -1, sizeof(f));
len = 0;
ll tmp = calc(n);
while (n) arr[++len] = n % 10, n /= 10;
note ans = dfs(len, 1, 1, 0, 0, 0);
return (tmp - ans.s3 + P) % P;
}
int main()
{
pow[0] = 1; for (int i = 1; i <= 38; i++) pow[i] = 1ll * pow[i - 1] * 10 % P;
scanf("%d", &T);
while (T--)
{
scanf("%lld%lld%d", &l, &r, &a);
printf("%lld\n", (solve(r) - solve(l - 1) + P) % P);
}
return 0;
}