算法提高课|AcWing 1086 恨7不成妻

题目描述:
单身!
依然单身!
吉哥依然单身!
DS 级码农吉哥依然单身!
所以,他平生最恨情人节,不管是 214 还是 77,他都讨厌!
吉哥观察了 214 和 77 这两个数,发现:
2+1+4=7
7+7=7×2
77=7×11
最终,他发现原来这一切归根到底都是因为和 7 有关!
所以,他现在甚至讨厌一切和 7 有关的数!
什么样的数和 7 有关呢?
如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:

  1. 整数中某一位是 7;
  2. 整数的每一位加起来的和是 7 的整数倍;
  3. 这个整数是 7 的整数倍。
现在问题来了:吉哥想知道在一定区间内和 7 无关的整数的平方和。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据占一行,包含两个整数 L 和 R。
输出格式
对于每组数据,请计算 [L,R]中和 7 无关的数字的平方和,并将结果对 10^9+7取模后输出。
数据范围
1≤T≤50,
1≤L≤R≤10^18
输入样例:
3 1 9 10 11 17 17

输出样例:
236 221 0

分析:
本题是比较难的数位DP问题,思路并不复杂,只是代码较为繁琐,因为数字是long long类型才能存的下的,所以在计算一连串乘法时需要不停的取模,比如long long a,b; 计算a * b就应该写成(a % P) * (b % P) % P,其中P = 1e9 + 7,另外对于int类型的数组,如果计算的中间结果超过了int,需要及时的强转为long long,忽视其中任何一步可能爆int的乘法运算,都会引起结果的错误。下面正式分析本题的解法。
直接dfs的话思路比较简洁,但是不方便进行记忆化搜索,需要存储的东西太多了,而不进行记忆化搜索显然会超时,所以还是采用动态规划的解法。一般数位DP的题目用f[i][j]表示开头是j的i位数的满足条件的数字的个数,比如不含7的i位数的个数就很容易用f[i][j]表示出来,但是题目新增了两个条件,这个数不能被7整除,这个数的每一位之和不能被7整除,所以对整数的划分应该划分为这个数模上7的余数以及数的各位之和模上7的余数这两个等价类,因此要对f数组增加两维。f[i][j][a][b]表示一个i为数最高位是j,各位之和模上7等于a,这个数模上7等于b,注意这里还没有给出f数组的含义,因为如果只是统计这样数的个数,那么本题就相当简单了,但是题目要求的是与7无关整数的平方和,这就相当复杂了,如果我们算到最后一位才去加上这个数的平方和,超时是肯定的,我们需要求的是i位数中以j开头的满足条件的整数的平方和是多少,需要有明确的递推式。下面开始推公式:
设满足条件的以j开头的i位数有jx1,jx2,...,jxn。其中xi表示j后面的i-1位数,我们要求这些数的平方和S,也就是S = (jx1)^2 + ... + (jxn)^2。又jxi = j * 10^(i-1) + xi,故S = (j*10^(i-1))^2 + 2 * j * 10^(i-1) * x1+ x1^2) + ... + (j*10^(i-1))^2 + 2 * j * 10^(i-1) * xn + xn^2) = n * j*10^(i-1))^2 + 2 * j * 10^(i-1) * (x1+...+xn) + (x1^2 +...+ xn^2),观察表达式中出现的项,分别是满足条件数的个数n,这些数中后n-1位组成数字的和x1 + ... + xn,以及这些数后n-1位组成数字的平方和。所以f数组要存储的是以j开头满足条件的i位数的个数、和以及平方和,可以使用结构体存储,s0表示个数,s1表示和,s2表示平方和。下面要考虑的是,如何通过f[i-1][k][c][d]的s0,s1,s2推出f[i][j][a][b]的这些信息,首先考虑c和d的取值,i位数的各位之和是a,则前i-1位的和是a - j,故c = a - j,当然还要对7取模。又前i-1位数j * 10^(i-1) + d对7取模结果是b,所以d = b - j * 10^(i-1)再对7取模。令v1 = f[i][j][a][b],v2 = f[i-1][k][c][d]。显然v1.s0 += v2.s0。那么如何通过前i-1位数组成数字的和求出加上最高位j组成数字的和呢?jx1 + jx2 + ...+jxn = (j*10^(i-1) + x1)+...+(j*10^(i-1)+xn) = n * j * 10^(i-1) + (x1 + ... + xn)即v1.s1 += v2.s0 * j * 10^(i-1) + v2.s1,最后根据本段开头推出的公式得v1.s2 += v2.s0 * j*10^(i-1))^2 + 2 * j * 10^(i-1) * v2.s1 + v2.s2,当然代码在实行这部分时每步乘法的后面都需要加上取模运算,表达式会更加复杂。
现在我们知道了以j开头的i位数的与7无关的数的平方和是怎么有前面的j和后面的x推出来了,问题就解决了一大半,剩下的就是自高向低逐位枚举n的每一位了,需要存储以及枚举的位数的数字之和last1,以及已经枚举的数组成的数字last2。我们要求f[n][j][a][b],其中a与b都不能是0,我们不妨先求出能够使a和b是0的数是哪些,然后排除这些数即可。要想前面的数字last1与后面的数字组合起来的数与7无关,则需要求出f[i+1][j][c][d]中的c和d是多少时,(c + last1) % 7 == 0,即c = mod(-last1,7),并且(last2 * 10^(i+1) + d) % 7 == 0,d = mod(-last2 * 10^(i+1),7),(这里为什么是乘以10的i+1字方?是因为枚举第i位时last2存储的还是i+1前面的数)。我们知道了c和d取这些值时会使得最终的数与7有关,那么我们枚举所有的c和d不为该值时的数,将题目的平方和加起来就是我们需要求的结果了。
PS:可能即使看上面那么详细的推导过程还是一头雾水,看不懂的地方看代码时回过头来看分析过程就可以看懂了。本题就是一个取模麻烦,公式推导出来实现还是很简单的。
#include #include #include using namespace std; const int N = 20,P = 1e9 + 7; typedef long long ll; int p7[N],p9[N]; struct F{ int s0,s1,s2; }f[N][10][7][7]; int mod(ll x,int y){//取模函数,主要针对x是负数 return (x % y + y) % y; } void init(){ for(int i = 0; i < 10; i++){ if(i == 7)continue; auto &v = f[1][i][i%7][i%7]; v.s0++,v.s1 += i,v.s2 += i * i; } ll p = 10; for(int i = 2; i < N; i++,p *= 10){ for(int j = 0; j <= 9; j++){ if(j == 7)continue; for(int a = 0; a < 7; a++){ for(int b = 0; b < 7; b++){ for(int k = 0; k <= 9; k++){ if(k == 7)continue; auto &v1 = f[i][j][a][b],&v2 = f[i-1][k][mod(a - j,7)][mod(b - p * j,7)]; v1.s0 = mod(v1.s0 + v2.s0,P); v1.s1 = mod(v1.s1 + v2.s1 + j * (p % P) % P * v2.s0,P); v1.s2 = mod(v1.s2 + j * j * (p % P) % P * (p % P) % P * v2.s0 + 2 * j * p % P * v2.s1 + v2.s2,P); } } } } } p7[0] = p9[0] = 1; //预处理10的若干字方对7和P取模的结果,打表备查 for(int i = 1; i < N; i++){ p7[i] = (p7[i - 1] * 10) % 7; p9[i] = (ll)p9[i - 1] * 10 % P; //这里如果不强转为ll中间结果会爆int } } F getf(int x,int y,int a,int b){//找出第三维第四维不是a和b的数的个数、和以及平方和 int s0 = 0,s1 = 0,s2 = 0; for(int i = 0; i < 7; i++){ if(i == a)continue; for(int j = 0; j < 7; j++){ if(j == b)continue; auto &v = f[x][y][i][j]; s0 = (s0 + v.s0) % P; s1 = (s1 + v.s1) % P; s2 = (s2 + v.s2) % P; } } return {s0,s1,s2}; } int get(ll n){ if(!n)return 0; ll m = n; vector num; while(n)num.push_back(n % 10),n /= 10; int res = 0; ll last1 = 0,last2 = 0; for(int i = num.size() - 1; i >= 0; i--){ int x = num[i]; for(int j = 0; j < x; j++){ if(j == 7)continue; int a = mod(-last1,7),b = mod(-last2 * p7[i + 1],7); auto v = getf(i + 1,j,a,b); res = mod(res + last2 % P * (last2 % P) % P * p9[i + 1] % P * p9[i + 1] % P * v.s0 % P + 2 * last2% P * p9[i + 1] % P * v.s1 % P + v.s2,P); } if(x == 7)break; last1 += x,last2 = last2 * 10 + x; if(!i && last1 % 7 && m % 7)res = (res + m % P * (m % P)) % P; } return res; } int main(){ int T; ll L,R; cin>>T; init(); while(T--){ cin>>L>>R; cout<

【算法提高课|AcWing 1086 恨7不成妻】

    推荐阅读