[HDU]4507 恨7不成妻 数位Dp好题

本文介绍了一个有趣的编程问题,主人公吉哥因个人情感原因讨厌与数字7相关的整数。文章详细解析了一道关于寻找特定区间内与7无关的数字的平方和的问题,通过数位DP的方法给出了具体的解决方案。

吉哥系列故事——恨7不成妻

Time Limit: 1000/500 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)
Total Submission(s): 4851 Accepted Submission(s): 1590

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

Source
2013腾讯编程马拉松初赛第一场(3月21日)

题解

这道题真的是数位Dp好题, 并不是好在他的思路有多么巧妙, 因为实际上这道题没有很高深的思路. 而是好在它很好的较全面的考察了数位Dp的能力以及对其的理解.

首先3个条件很好搞. 这也算复习了数位Dp的经典套路.

对于:
1、整数中某一位是7
方法: 你不for7就可以了.

2、整数的每一位加起来的和是7的整数倍
方法: 设一维专门维护当前维数位和mod7的余数是多少, 统计为0的即可, 这个在边界就可以处理.

3、这个整数是7的整数倍
方法: 同一上, 开一维专门维护数的模数, 这个只要模拟就可以了, 详见代码.

主要是求的不是数量而是和, 还是平方和.
对于当前这一位枚举到i的时候, 实际上你只需要递归处理好i-1之后, 再考虑加上i这一位, 那么在第i位满足条件的数, 只需要在递归处理好i-1后在前面添一个当前枚举的即可. 那么设当前枚举的是i, 在第pos位, 那么当前添了之后就是在原来的基础上加了 i * 10^pos这么多, 设这个为a. 那么新组成的数就是a + b. b为递归处理之后i-1满足的数字. 那么平方和就是(a + b)^2 = a^2 + b^2 + 2 * a * b. 那我们只需要维护i-1位的满足条件数的平方和 以及 i-1位满足条件的有多少个, i-1满足条件的数之和这三个就可以既维护好第i位的平方和的信息了.

具体转移详见代码. 由于有三个信息, 所以用结构体来存, 返回的也是结构体.

注意模意义下r算出来的的比l-1小, 减出来是负数, 所以要+mod再%mod. 这些都是对模数负数的一般处理.

#include<stdio.h>
#include<cstring>
typedef long long dnt;
const dnt p = 1e9 + 7;
int T, bit[22], len;
dnt pw[22];
struct point{
    dnt su, sq, cn;
    point() {cn = -1; su = sq =0;}
    point(dnt su, dnt sq, dnt cn): su(su), sq(sq), cn(cn){}
}dp[22][22][10];
point dfs(int pos, dnt mod, dnt sum, bool lim){
    if(!pos) return (mod && sum) ? point(0, 0, 1) : point(0, 0, 0);
    if(~dp[pos][mod][sum].cn && !lim) return dp[pos][mod][sum];
    point ans, now;
    ans.cn = 0;
    int ban = (lim) ? bit[pos] : 9;
    for(int i = 0; i <= ban; ++i){
        if(i == 7) continue;
        dnt nsum = (sum + i) % 7; 
        dnt nmod = (mod * 10 + i) % 7;
        now = dfs(pos - 1, nmod, nsum, lim && i == ban);
        dnt a  = i * pw[pos] % p, aa = a * a % p;
        ans.cn = (ans.cn + now.cn) % p;
        ans.su = (ans.su + now.cn * a % p + now.su) % p; 
        ans.sq = (ans.sq + aa * now.cn % p + 2 * a * now.su % p + now.sq) % p;
    }
    if(!lim) dp[pos][mod][sum] = ans;
    return ans;
}
inline void resolve(dnt x){
    len = 0;
    while(x) bit[++len] = x % 10, x /= 10;
}
inline dnt solve(dnt x){
    resolve(x);
    point c = dfs(len, 0, 0, true);
    return c.sq;
}
int main(){
    scanf("%d", &T);
    pw[1] = 1;
    for(int i = 2; i <= 20; ++i) pw[i] = pw[i - 1] * 10 % p;
    while(T--){
        dnt l, r;
        scanf("%I64d%I64d", &l, &r);
        dnt ed = solve(r);
        ed -= solve(l - 1);
        printf("%I64d\n", (ed % p + p) % p);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值