HDU - 4507 吉哥系列故事――恨7不成妻 (数位DP,推公式啊)

8 篇文章 0 订阅


单身!
  依然单身!
  吉哥依然单身!
  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

题目大意:求指定范围内与7不沾边的所有数的平方和。结果要mod 10^9+7(鬼畜の元凶)

解题思路

与7不沾边的数需要满足三个条件。

①不出现7

②各位数和不是7的倍数

③这个数不是7的倍数

这三个条件都是基础的数位DP。

但是这题要统计的不是符合条件个数,而是平方和。

也就是说在DP时候,要重建每个数,算出平方,然后求和。

需要维护三个值(推荐使用结构体), 假定dfs推出返回的结构体是next,当前结果的结构体是ans

①符合条件数的个数 cnt

②符合条件数的和 sum

③符合添加数的平方和 sqsum

其中①是基础数位DP。②next.sum+(10^len*i)*ans.cnt,其中(10^len*i)*ans.cnt代表以len为首位的这部分数字和。

③首先重建一下这个数,(10^len*i+x),其中x是这个数的后面部分,则平方和就是(10^len*i)^2+x^2+2*10^len*i*x,其中x^2=next.sqsum

整体还要乘以next.cnt,毕竟不止一个。

这样sqsum+=next.sqsum

sqsum+=(2*10^len*i*x)*next.cnt=(2*10^len*i)*next.sum(神奇的化简

sqsum+=(10^len*i)^2*next.cnt

 

然后就是本题鬼畜的地方了,cnt,sum,sqsum,三个都是达到了int64极限。

也即是说凡是这三个值参与运算的地方,都要狠狠打上mod,尤其是cnt!一坨坨mod出现了。

 

mod之后统计函数也有个小陷阱,那就是f(r)在mod之后有可能小于f(l-1)。也就是要对负数取正数模。

负数取模的方法(ans%mod+mod)%mod。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
struct node
{
    ll cnt;
    ll sum;
    ll ret;
}dp[20][10][7][7][2];
ll pow10[22];
int b[22];
node dfs(int pos,int pre,int r1,int r2,int statue,int limit)
{
    int i,j;
    if(pos<0) {
        node t={1,0,0};
        if(r1==0 || r2==0 || statue==1) t.cnt=0;
        return t;
    }
    if(!limit && dp[pos][pre][r1][r2][statue].cnt!=-1) return dp[pos][pre][r1][r2][statue];
    int end=limit?b[pos]:9;
    node ans={0,0,0};
    for(i=0;i<=end;i++) {
        node next = dfs(pos-1,i,(r1+i)%7,(r2*10+i)%7,statue||i==7,limit && i==end);
        ans.cnt+=next.cnt;
        ans.cnt%=mod;
        ans.sum+=(next.sum+((pow10[pos]*i)%mod)*next.cnt%mod)%mod;
        ans.sum%=mod;
        ans.ret+=(next.ret+((2*pow10[pos]*i)%mod)*next.sum)%mod;
        ans.ret%=mod;
        ans.ret+=((next.cnt*pow10[pos])%mod*pow10[pos]%mod*i*i%mod);
        ans.ret%=mod;
    }
    if(!limit) dp[pos][pre][r1][r2][statue]=ans;
    return ans;
}
ll work(ll num)
{
    int cnt=0;
    memset(b,0,sizeof(b));
    while(num) {
        b[cnt++]=num%10;
        num/=10;
    }
    node tmp = dfs(cnt-1,9,0,0,0,1);
    return tmp.ret;
}
int main()
{
    int T,i,j;
    ll l,r;
    memset(dp,-1,sizeof(dp));
    pow10[0]=1;
    for(i=1;i<=18;i++) {
        pow10[i]=pow10[i-1]*10%mod;
    }
    cin>>T;
    while(T--) {
        cin>>l>>r;
        ll ans = (work(r)-work(l-1)+mod)%mod;
        cout<<ans<<endl;
    }
    return 0;
}










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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值