恨7不成妻

恨7不成妻

题目描述

在这里插入图片描述


核心思路

一般数位DP的题目用f[i][j]表示开头是j的i位数的满足条件的数字的个数,比如不含7的i位数的个数就很容易用f[i][j]表示出来,但是题目新增了两个条件,这个数不能被7整除,这个数的每一位之和不能被7整除,所以对整数的划分应该划分为(这个数模上7的余数)以及(数的各位之和模上7的余数)这两个等价类,因此要对f数组增加两维。 f [ i ] [ j ] [ a ] [ b ] f[i][j][a][b] f[i][j][a][b]表示一共有i位,最高位数字是j,各位之和模上7等于a,这个数模上7等于b。

设满足条件以j开头的i位数有: j x 1 , j x 2 , ⋯   , j x n jx_1,jx_2,\cdots ,jx_n jx1,jx2,,jxn,其中 x i x_i xi表示j后面的i-1位数,举个例子,比如对于一个6位数213456,那么j=2, x i x_i xi=13456。我们要求这些数的平方和,那么形成的 j x i jx_i jxi的平方和就是 ( j ∗ 1 0 5 + x i ) 2 (j*10^5+x_i)^2 (j105+xi)2。设我们要求这些数的平方和S,也就是 S = ( j x 1 ) 2 + ( j x 2 ) 2 + ⋯ + ( j x n ) 2 S=(jx_1)^2+(jx_2)^2+\cdots +(jx_n)^2 S=(jx1)2+(jx2)2++(jxn)2,又因为 j x i = ( j ∗ 1 0 i − 1 + x i ) jx_i=(j*10^{i-1}+x_i) jxi=(j10i1+xi),所以 S = ( j ∗ 1 0 i − 1 + x 1 ) 2 + ( j ∗ 1 0 i − 1 + x 2 ) 2 + ⋯ + ( j ∗ 1 0 i − 1 + x n ) 2 = ( j ∗ 1 0 i − 1 ) 2 + 2 ∗ j ∗ 1 0 i − 1 ∗ x 1 + ( x 1 ) 2 + ( j ∗ 1 0 i − 1 ) 2 + 2 ∗ j ∗ 1 0 i − 1 ∗ x 2 + ( x 2 ) 2 + ⋯ + S=(j*10^{i-1}+x_1)^2+(j*10^{i-1}+x_2)^2+\cdots +(j*10^{i-1}+x_n)^2=(j*10^{i-1})^2+2*j*10^{i-1}*x_1+(x_1)^2+(j*10^{i-1})^2+2*j*10^{i-1}*x_2+(x_2)^2+\cdots + S=(j10i1+x1)2+(j10i1+x2)2++(j10i1+xn)2=(j10i1)2+2j10i1x1+(x1)2+(j10i1)2+2j10i1x2+(x2)2++ ( j ∗ 1 0 i − 1 ) 2 + 2 ∗ j ∗ 1 0 i − 1 ∗ x n + ( x n ) 2 (j*10^{i-1})^2+2*j*10^{i-1}*x_n+(x_n)^2 (j10i1)2+2j10i1xn+(xn)2= ( j ∗ 1 0 i − 1 ) 2 × n + 2 ∗ j ∗ 1 0 i − 1 ∗ ( x 1 + x 2 + ⋯ + x n ) + ( x 1 2 + x 2 2 + ⋯ + x n 2 ) (j*10^{i-1})^2\times n+2*j*10^{i-1}*(x_1+x_2+\cdots +x_n)+(x_1^2+x_2^2+\cdots +x_n^2) (j10i1)2×n+2j10i1(x1+x2++xn)+(x12+x22++xn2)。观察表达式中的项可知,分别要求满足条件数的个数n、这些数中后n-1位组成数字的和 x 1 + x 2 + ⋯ + x n x_1+x_2+\cdots +x_n x1+x2++xn、以及这些数后n-1位组成数字的平方和 x 1 2 + x 2 2 + ⋯ + x n 2 x_1^2+x_2^2+\cdots +x_n^2 x12+x22++xn2

因此,f数组需要存储的是以j开头满足条件的i位数的个数、和以及平方和,那么可以用结构体来存储, s 0 s_0 s0表示个数, s 1 s_1 s1表示和, s 2 s_2 s2表示平方和。

如何通过 f [ i − 1 ] [ k ] [ c ] [ d ] f[i-1][k][c][d] f[i1][k][c][d] s 0 、 s 1 、 s 2 s_0、s_1、s_2 s0s1s2来推出 f [ i ] [ j ] [ a ] [ b ] f[i][j][a][b] f[i][j][a][b]的信息呢?

首先考虑如何求出c和d。现已知i位数的各位之和是a,且第i位上的数字是j,i-1位数的各位之和是c,那么有 c + j = a c+j=a c+j=a,因此 c = a − j c=a-j c=aj,当然结果还要模7,即 c = ( a − j ) % 7 c=(a-j)\%7 c=(aj)%7

前i-1位数 j ∗ 1 0 i − 1 + d j*10^{i-1}+d j10i1+d对7取模结果为 b b b,因此, d = b − j ∗ 1 0 i − 1 d=b-j*10^{i-1} d=bj10i1,当然结果还要对7取模,即 d = ( b − j ∗ 1 0 i − 1 ) % 7 d=(b-j*10^{i-1})\%7 d=(bj10i1)%7。于是,我们就可以求出了c和d的值了。

如何确定状态转移方程呢?

v 1 = f [ i ] [ j ] [ a ] [ b ] v1=f[i][j][a][b] v1=f[i][j][a][b] v 2 = f [ i − 1 ] [ k ] [ c ] [ d ] v2=f[i-1][k][c][d] v2=f[i1][k][c][d],我们需要用 v 2 v2 v2的信息来更新 v 1 v1 v1的信息。显然, v 1. s 0 = v 1. s 0 + v 2. s 0 v1.s_0=v1.s_0+v2.s_0 v1.s0=v1.s0+v2.s0。那么如何通过前i-1位数组成数字的和求出加上最高位j组成数字的和呢?即如何用 v 2. s 1 v2.s_1 v2.s1来更新 v 1. s 1 v1.s_1 v1.s1呢?因为 j x 1 + j x 2 + ⋯ + j x n jx_1+jx_2+\cdots +jx_n jx1+jx2++jxn= ( j ∗ 1 0 i − 1 + x 1 ) + ( j ∗ 1 0 i − 1 + x 2 ) + ⋯ + ( j ∗ 1 0 i − 1 + x n ) = n ∗ j ∗ 1 0 i − 1 + ( x 1 + x 2 + ⋯ + x n ) (j*10^{i-1}+x_1)+(j*10^{i-1}+x_2)+\cdots +(j*10^{i-1}+x_n)=n*j*10^{i-1}+(x_1+x_2+\cdots +x_n) (j10i1+x1)+(j10i1+x2)++(j10i1+xn)=nj10i1+(x1+x2++xn),其中 n n n是v2中的 v 2. s 0 v2.s_0 v2.s0 ( x 1 + x 2 + ⋯ + x n ) (x_1+x_2+\cdots +x_n) (x1+x2++xn)是v2中的 v 2 . s 1 v_2.s_1 v2.s1,因此 v 1. s 1 + = v 2. s 0 ∗ j ∗ 1 0 i − 1 + v 2. s 1 v1.s_1+=v2.s_0*j*10^{i-1}+v2.s_1 v1.s1+=v2.s0j10i1+v2.s1。那么如何求出平方和呢?由前面我们推导出的那个S式子可知, v 1. s 2 + = v 2. s 0 ∗ ( j ∗ 1 0 i − 1 ) 2 + 2 ∗ j ∗ 1 0 i − 1 ∗ v 2. s 1 + v 2. s 2 v1.s_2+=v2.s_0*(j*10^{i-1})^2+2*j*10^{i-1}*v2.s_1+v2.s_2 v1.s2+=v2.s0(j10i1)2+2j10i1v2.s1+v2.s2。当然代码在实行这部分时每步乘法的后面都需要加上取模运算,表达式会更加复杂。

现在我们知道了以j开头的i位数的与7无关的数的平方和是怎么由前面的j和后面的x推出来了,问题就解决了一大半,剩下的就是自高向低逐位枚举n的每一位了,我们设 l a s t 1 last1 last1用来记录已经枚举的位数的数字之和,设 l a s t 2 last2 last2用来记录已经枚举的那些位所组成的数字。要想前面的数字last2与后面的数字组合起来得到的数与7无关,则需要求出 f [ i + 1 ] [ j ] [ c ] [ d ] f[i+1][j][c][d] f[i+1][j][c][d]中的c和d是多少时,使得 ( c + l a s t 1 ) % 7 = = 0 (c+last1)\%7==0 (c+last1)%7==0,解得 c = ( − l a s t 1 ) % 7 c=(-last1)\%7 c=(last1)%7,使得 ( l a s t 2 ∗ 1 0 i + 1 + d ) % 7 = = 0 (last2*10^{i+1}+d)\%7==0 (last210i+1+d)%7==0,解得 d = ( − l a s t 2 ∗ 1 0 i + 1 ) % 7 d=(-last2*10^{i+1})\%7 d=(last210i+1)%7,为什么是i+1次方而不是i次方呢?因为枚举第i位时last2存储的还是i+1前面的数,或者可以理解为从第0位到第i位总共由i+1位数。我们知道了c和d取这些值时会使得最终的数与7有关,那么我们枚举所有的c和d不为该值时的数,将题目的平方和加起来就是我们想求的结果。

在这里插入图片描述


代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 20,P = 1e9 + 7;
typedef long long LL;
int power7[N],power9[N];
struct F{
    int s0,s1,s2;	//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 power = 10;
    for(int i = 2;i < N;i++,power *= 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 - power * j,7)];
                        v1.s0 = mod(v1.s0 + v2.s0,P);
                        v1.s1 = mod(v1.s1 + v2.s1 + j * (power % P) % P * v2.s0,P);
                        v1.s2 = mod(v1.s2 + j * j * (power % P) % P * (power % P) % P * v2.s0 + 2 * j * power % P * v2.s1 + v2.s2,P);
                    }
                }
            }
        }
    }
    power7[0] = power9[0] = 1;//预处理10的若干字方对7和P取模的结果,打表备查
    for(int i = 1;i < N;i++){
        power7[i] = (power7[i - 1] * 10) % 7;
        power9[i] = (LL)power9[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;//我们要找到的是第三维上的值不等于a的数,如果相等则跳过
        for(int j = 0;j < 7;j++){
            if(j == b)  continue;//我们要找到的是第三维上的值不等于b的数,如果相等则跳过
            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<int> num;
    while(n)    num.push_back(n % 10),n /= 10;
    int res = 0;
    //last1用来记录已经枚举的位数的数字之和  last2记录已经枚举的那些位所组成的数字
    LL last1 = 0,last2 = 0;
    //从高位枚举到低位
    for(int i = num.size() - 1;i >= 0;i--){
        int x = num[i];//取出当前位上的数
        //走左侧分支,可选数的范围是[0,x-1]
        for(int j = 0;j < x;j++){
            if(j == 7)  continue;
            //这里求出的是最终构成的那个数它是与7有关的,在这个前提下,求出了a和b
            //但是我们要求的是与7无关的数哦
            int a = mod(-last1,7),b = mod(-last2 * power7[i + 1],7);
            //通过getf函数找到与7无关的数v
            auto v = getf(i + 1,j,a,b);
            res = mod(res + last2 % P * (last2 % P) % P * power9[i + 1] % P * power9[i + 1] % P * v.s0 % P
            + 2 * last2  % P * power9[i + 1] % P * v.s1 % P + v.s2,P);
        } 
        //走右侧分支,选定了x这个数,继续枚举右侧分支
        if(x == 7)  break;
        last1 += x,last2 = last2 * 10 + x;//更新last1和last2
        //如果走到了最右下角,说明n这个数它本身就是满足要求的
        if(!i && last1 % 7 && last2 % 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<<mod(get(R) - get(L - 1),P)<<endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值