HDU 4507 恨7不成妻 (数位DP套路题,详细解析)

不会数位 D P DP DP的这里指路一篇介绍非常详细的数位 D P DP DP b l o g blog blog:点这里。

  • 链接
    恨7不成妻

  • 题面

    单身!
    依然单身!
    吉哥依然单身!
    DS级码农吉哥依然单身!
    所以,他生平最恨情人节,不管是214还是77,他都讨厌!
    吉哥观察了214和77这两个数,发现:
    2 + 1 + 4 = 7 2+1+4=7 2+1+4=7 
    7 + 7 = 7 ∗ 2 7+7=7*2 7+7=72
    77 = 7 ∗ 11 77=7*11 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
    
  • 解题思路

    根据题意我们做出预处理,利用闫式 D P DP DP分析法分析如下:

以上只是简单分析,我们还并没有真正的进行状态转移和计算,那么根据题意,首先是需要知道整数的每一位加起来的和是 7 7 7的整数倍以及该整数是 7 7 7的整数倍,这个好处理,在我们的前面的题中有类似的题型,这已经在我们的 f f f数组的第三维和第四维了。所以难点就在于怎么处理整数的平方和。我们看下面的公式推导:

我们用 j A jA jA来表示 i i i位数,而其中的 A A A i − 1 i-1 i1位数。设这个状态有 t t t个符合要求的数,分别是 A 1 A_1 A1~ A t A_t At 那么,平方和易得为:

( j A 1 ) 2 + ( j A 2 ) 2 + ( j A 3 ) 2 + . . . + ( j A t − 1 ) 2 + ( j A t ) 2 (jA_1)^2+(jA_2)^2+(jA_3)^2+...+(jA_{t-1})^2+(jA_t)^2 (jA1)2+(jA2)2+(jA3)2+...+(jAt1)2+(jAt)2

(我们分割表示将 A A A提取出来。)

= ( j ∗ 1 0 i − 1 + A 1 ) 2 + ( j ∗ 1 0 i − 1 + A 2 ) 2 + ( j ∗ 1 0 i − 1 + A 3 ) 2 + . . . + ( j ∗ 1 0 i − 1 + A t − 1 ) 2 + ( j ∗ 1 0 i − 1 + A t ) 2 =(j*10^{i-1}+A_1)^2+(j*10^{i-1}+A_2)^2+(j*10^{i-1}+A_3)^2+...+(j*10^{i-1}+A_{t-1})^2+(j*10^{i-1}+A_t)^2 =(j10i1+A1)2+(j10i1+A2)2+(j10i1+A3)2+...+(j10i1+At1)2+(j10i1+At)2

(平方和公式)

= t ∗ ( j ∗ 1 0 i − 1 ) 2 + 2 ∗ ( j ∗ 1 0 i − 1 ) ∗ ( A 1 + . . . + A t ) + ( A 1 2 + . . . + A 2 ) =t*(j*10^{i-1})^2+2*(j*10^{i-1})*(A_1+...+A_t)+(A_1^2+...+A^2) =t(j10i1)2+2(j10i1)(A1+...+At)+(A12+...+A2)

这样,在这个式子中,由于 j j j已知,所以我们发现 f f f数组需要保存三个值。 A A A 0 0 0次方之和,也就是符合要求的数, A A A 1 1 1次方之和,也就是符合要求的除去 j j j i − 1 i-1 i1位数相加, A A A 2 2 2次方之和,也就是符合要求的除去 j j j i − 1 i-1 i1位数平方相加。我们分别用 s 0 , s 1 , s 2 s_0,s_1,s_2 s0,s1,s2

分别代表上述的三个值。

那么这里我们需要怎么求 s 1 s_1 s1,如下:

注:这里的 s 1 s_1 s1 i + 1 i+1 i+1位的 s 1 s_1 s1,而它存储的就是 i i i位的 A A A

j A 1 + . . . + j A t jA_1+...+jA_t jA1+...+jAt

= j ∗ 1 0 i − 1 + ( A 1 + . . . + A t ) =j*10^{i-1}+(A_1+...+A_t) =j10i1+(A1+...+At)

所以我们的 f f f应该是一个结构体数组,它需要存取 s 0 , s 1 , s 2 s_0,s_1,s_2 s0,s1,s2。那么预处理根据上述分析其实就简单了。那么就按照数位 D P DP DP的套路解决这道题即可。需要注意这道题好多坑点,多取模,足够细心才可以解决。(调 B u g Bug Bug调了好久。快绝望了。)

  • 代码
/**
  *@filename:恨7不成妻
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-05-12 21:19
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 20;
const ll P = 1e9+7;

//需要满足三个性质。
//1.不含7.
//2.各位数字之和模7不为0.an-1+...+a0%7!=0. 
//3.该数模7不为0.an-1*pow(10,n-1)+...+a0+pow(10,0)%7!=0

struct F{
    ll s0,s1,s2;//s0为符合要求的数。s1为符合要求的数1次方之和,s2为符合要求的数的2次方之和。
}f[N][10][7][7];//f[i][j][k][u]表示总共有i位数且最高位是j,该数值模7为k,各位数数字之和模7为u的所有数的s0,s1,s2.
//进行初始化。
int t;//测试数。
ll l,r;
ll power7[N],power9[N];//power7[i]存储10^i余7的余数,power9[i]存储10^i余P的余数。
ll mod(ll x,ll y){
    return (x%y+y)%y;
}
void init(){
    //确定初始值,位数为1的情况。
    for(int j=0;j<10;j++){
        if(j==7)continue;
        //根据性质排除不符合要求的。
        F &v=f[1][j][j%7][j%7];//这里用引用减少代码量。
        v.s0++;
        v.s1+=j;
        v.s2+=j*j;
    }
    ll power = 10;//辅助作用,表示10的i-1次方。
    for(int i=2;i<N;i++,power*=10){
        for(int j=0;j<10;j++){
            if(j==7)continue;//排除不符合要求的数。
            for(int k=0;k<7;k++){
                for(int u=0;u<7;u++){
                    for(int q=0;q<10;q++){
                        //枚举i-1的最高位。
                        if(q==7)continue;
                        F &x=f[i][j][k][u],y=f[i-1][q][mod(k-j*(power%7),7)][mod(u-j,7)];
                        //s0,s1,s2都是通过公式就算得到。
                        x.s0=mod(x.s0+y.s0,P);
                        x.s1=mod(x.s1+1LL*j%P*(power%P)%P*y.s0%P+y.s1,P);
                        x.s2=mod(x.s2+
                            1LL*j%P*y.s0%P*(power%P)%P*j%P*(power%P)%P+
                            1LL*y.s1%P*2%P*j%P*(power%P)%P+y.s2,P);
                    }
                }
            }
        }
    }
    //这里处理为了方便以及降低时间复杂度。
    power7[0]=1,power9[0]=1;
    for(int i=1;i<N;i++){
        power7[i]=power7[i-1]*10%7;
        power9[i]=power9[i-1]*10%P;
    }
}
F get(int i,int j,int k,int u){
    //因为f[i][j][k][u]是本身模7等于k,且各位数之和模7等于u的,所以我们需要找出符合条件的集合。
    ll s0=0,s1=0,s2=0;
    for(int x=0;x<7;x++){
        for(int y=0;y<7;y++){
            if(x==k||y==u)continue;
            F v=f[i][j][x][y];
            s0=mod(s0+v.s0,P);
            s1=mod(s1+v.s1,P);
            s2=mod(s2+v.s2,P);
        }
    }
    return {s0,s1,s2};
}
ll dp(ll n){
    if(!n)return 0;//0的平方和为0.
    vector<int> a;
    ll temp=n%P;//备份一个n,供后面判断n使用。
    while(n)a.push_back(n%10),n/=10;
    ll last_a=0,last_b=0;//这里我们需要存储前缀的本身值和前缀的个位数之和。
    ll ans=0;//答案。
    for(int i=a.size()-1;i>=0;i--){
        int x=a[i];
        for(int j=0;j<x;j++){
            //走左分支。
            if(j==7)continue;
            //我们需要将符合条件的数筛出来,这里要用到一个get函数。
            //求得本身模7不等于a,并且各位数之和模7不等b的集合,此时就可以使用预处理出来的结构体
            int k=mod(-last_a*power7[i+1],7),u=mod(-last_b,7);
            F v=get(i+1,j,k,u);
            //cout<<v.s0<<" "<<v.s1<<" "<<v.s2<<endl;
            //根据公式求解s2.
            //j就是last_a.
            ans=mod(ans+
                1LL*(last_a%P)*(last_a%P)%P*(power9[i+1]%P)%P*(power9[i+1]%P)%P*v.s0%P+
                1LL*2*last_a%P*(power9[i+1]%P)%P*v.s1%P+
                v.s2,P);
            //cout<<ans<<endl;
        }
        //判断x。
        if(x==7)break;
        //走右分支更新。
        last_a=last_a*10+x;
        last_b+=x;
        //判断自己本身是否符合要求。
        if(!i&&last_a%7&&last_b%7){
            ans=mod(ans+temp*temp%P,P);
        }
    }
    return ans;
}
int main(){
    init();
    cin>>t;
    while(t--){
        cin>>l>>r;
        cout<<mod(dp(r)-dp(l-1),P)<<endl;
    }
    return 0;
}
/*
1
1 1000000000000000000
*/
posted @   unique_pursuit  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示