HDU-4507
HDU-4507
题面
单身!
依然单身!
吉哥依然单身!
DS级码农吉哥依然单身!
所以,他生平最恨情人节,不管是214还是77,他都讨厌!
吉哥观察了214和77这两个数,发现:
2+1+4=7
7+7=72
77=711
最终,他发现原来这一切归根到底都是因为和7有关!所以,他现在甚至讨厌一切和7有关的数!
什么样的数和7有关呢?
如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
1、整数中某一位是7;
2、整数的每一位加起来的和是7的整数倍;
3、这个整数是7的整数倍;
现在问题来了:吉哥想知道在一定区间内和7无关的数字的平方和。
Input
输入数据的第一行是case数T(1 <= T <= 50),然后接下来的T行表示T个case;每个case在一行内包含两个正整数L, R(1 <= L <= R <= 10^18)。
Output
请计算[L,R]中和7无关的数字的平方和,并将结果对10^9 + 7 求模后输出。
Sample Input
3
1 9
10 11
17 17
Sample Output
236
221
0
思路
数位DP,我通过这篇博客学习
与博客中普通的数位DP计数思路不同,本题需要求解所有数的平方和。
由数位DP上一位推往这一位的列公式可得,上一位(x)这一位(y):
(10*x+y)^2 = 100*x^2 + y^2 + 20*x*y
……公式1
所以转移方程就变为:
ans.cnt = ans.cnt + temp.cnt
有cnt个数满足第pos位是i,所以有:
ans.sum = temp.sum + i*len[pos]*temp.cnt
所以由公式1可以得到:
ans.sum2+=i*len[pos]*i*len[pos]*temp.cnt+temp.sqsum+2*i*len[pos]*temp.sum
记得每次乘后要对题目要求的mod取余,否则数字过大会超过LL的范围
记录sign1:所有位数和,sign2:该数
存在dp数组中,方便下次搜索到该位且拥有相同的sign1和sign2时,可以直接返回结果。
代码
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
int num[30];
ll len[30];
struct node {
ll cnt;
ll sum; // 存数位累加和
ll sqsum; // 存数位平方累加和
node(int c=0, int s=0, int sq=0) : cnt(c), sum(s), sqsum(sq) {;}
} dp[20][10][10];
node dfs(int pos,int sign1, int sign2, bool limit) {
// sign1 记录各个位和
// sign2 记录该数和
if(pos == -1) {
node temp(0, 0, 0);
if(sign1 && sign2) {
temp.cnt = 1;
}
return temp;
}
if(!limit && dp[pos][sign1][sign2].cnt != -1) {
return dp[pos][sign1][sign2];
}
int up = limit ? num[pos] : 9;
node ans(0, 0, 0);
for(int i=0;i<=up;i++) {
if(i == 7) {
continue;
}
node temp = dfs(pos-1, (sign1+i)%7, (sign2*10+i)%7, limit && num[pos] == i);
ans.cnt = (ans.cnt + temp.cnt) % mod;
// ans.sum = temp.sum + i*len[pos]*temp.cnt
ans.sum=(ans.sum+(i*len[pos]%mod*temp.cnt%mod+temp.sum)%mod)%mod;
// ans.sum2+=i*len[pos]*i*len[pos]*temp.cnt+temp.sqsum+2*i*len[pos]*temp.sum
ans.sqsum=(ans.sqsum+(i*len[pos]%mod*i%mod*len[pos]%mod*temp.cnt%mod+temp.sqsum)%mod+2*i%mod*len[pos]%mod*temp.sum%mod)%mod;
}
if(!limit) {
dp[pos][sign1][sign2] = ans;
}
return ans;
}
ll solve(ll x) { // 分解出各个位
memset(dp, -1, sizeof(dp));
int sign = 0;
while(x) {
num[sign++] = x % 10;
x/=10;
}
return dfs(sign-1, 0, 0, true).sqsum % mod;
}
int main() {
len[0] = 1;
for(int i=1;i<20;i++) {
len[i] = len[i-1] * 10 % mod;
}
int n;
cin >> n;
while(n--) {
ll l, r;
cin >> l >> r;
cout << ((solve(r) - solve(l-1)) % mod + mod) % mod << endl;
}
}