HDU 4507 吉哥系列故事——恨7不成妻(数位DP+详解)

题目连接

 单身! 
  依然单身! 
  吉哥依然单身! 
  DS级码农吉哥依然单身! 
  所以,他生平最恨情人节,不管是214还是77,他都讨厌! 
   
  吉哥观察了214和77这两个数,发现: 
  2+1+4=7 
  7+7=7*2 
  77=7*11 
  最终,他发现原来这一切归根到底都是因为和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

题意:

      如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关
  1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍;

求一个区间中与7无关的数的平方和

题解:我们首先,要算一段数的平方和应该怎么算,例如: (20 + 3)^2 = 20^2 + 2*20*3 + 3^2。20-23的平方和 
= (20 + 1)^2 + (20 + 2)^2 + (20 + 3)^2 = 3*20^2 + 2*20*(1+2+3) + 1^2 + 2^2 + 3^2。例如当前为为pos,这里面包含三部分,一个是 (3*20^2)里面的3,表示yu7无关数的个数; 2*20*(1+2+3)里的(1+2+3)表示与7无关数的和;1^2 + 2^2 + 3^2这个就表示pos-1为的无关数的平方和。

所以我们要同时维护三个值,第一个无关数的个数很好维护,就是常规数位DP,第二个就是无关数的和,第三个就是无关数的平方和。细节看代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>
const int maxn=1e3+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
#define me(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define mid (l+r)/2
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
int dir[4][2]= {0,1,0,-1,1,0,-1,0};
typedef long long ll;
using namespace std;
ll d[20];
int num[20];
struct node
{
    ll cnt,sum,qsum;///cnt表示无关数的个数,sum表无关数的和,qsum表示无关数的平方和
} dp[20][10][10];
void init()///初始化
{
    d[0]=1;///d[i]=10^i;
    for(int i=1; i<=18; i++)
        d[i]=(d[i-1]*10)%mod;
    for(int i=0; i<20; i++)
        for(int j=0; j<10; j++)
            for(int k=0; k<10; k++)
                dp[i][j][k].cnt=-1;
}
node dfs(int pos,int s_y,int s_s,int lit)
{
    if(pos==-1)
    {
        node temp;
        temp.cnt=(s_y&&s_s);///只有当余数都不为0时才与7无关
        temp.sum=temp.qsum=0;
        return temp;
    }
    if(!lit&&dp[pos][s_y][s_s].cnt!=-1)
        return dp[pos][s_y][s_s];
    int End=lit?num[pos]:9;
    node ans;
    ans.cnt=ans.qsum=ans.sum=0;
    for(int i=0;i<=End;i++)
    {
        if(i==7)
            continue ;
        node temp=dfs(pos-1,(s_y*10+i)%7,(i+s_s)%7,lit&&i==End);
        ans.cnt=(ans.cnt+temp.cnt)%mod;
        ans.sum=(ans.sum+temp.cnt*d[pos]%mod*i+temp.sum)%mod;
        ///例如前面和为23,现在pos=3,有4个数满足,所以当前和应是23+1000*4;
        ///(21+22+23)^2=3*20^2 + 2*20*(1+2+3) + 1^2 + 2^2 + 3^2
        ans.qsum=(ans.qsum+(2*d[pos]*i)%mod*temp.sum+temp.qsum)%mod;///相当于上式里的+2*20*(1+2+3) + 1^2 + 2^2 + 3^2
        ans.qsum=(ans.qsum+(temp.cnt*d[pos])%mod*d[pos]%mod*i*i%mod)%mod;///相当于上式里的+3*20^2
    }
    if(!lit)
        dp[pos][s_y][s_s]=ans;
    return ans;
}
ll solve(ll n)
{
    int len=0;
    while(n)
    {
        num[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,0,0,1).qsum%mod;
}
int main()
{
    int t;
    init();
    scanf("%d",&t);
    while(t--)
    {
        ll l,r;
        scanf("%lld%lld",&l,&r);
        printf("%lld\n",(solve(r)-solve(l-1)+mod)%mod);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值