HDU - 4507 数位dp

题意:

如果一个整数符合下面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

思路:

好题啊!以为数位dp只能计数,没想到还能搞平方和,虽然也是由基本的计数推导的。
这道题如果只问这样的数的个数cnt,那就很好想了,设计状态dp[pos][pre_sum][pre_mod]表示的是不受limit影响的,位数为pos,到目前的数位和%7为pre_sum,构建到当前位的总数%7为pre_mod,这样的数中合法的数的个数。那么套用数位dp模板就可以解决。
但是这里要求的是平方和。所以就来了个有趣的套路。
我们利用一个结构体node {cnt, sum, sqsum}表示当前位置所计算的合法数的个数,总和,以及他们的平方和。
假设res是dfs递归中的上一层结果,当前位数选择的是i,然后递归求得下一层的结果是tmp,那么就有几个递推式可以利用。
显然,res.cnt = 所有 tmp.cnt的和;
假设当前pos位选择的是位数是i,然后接着递归pos-1位到0位得到的数是x,那么加上pos位这个数y = i * (10^pos) + x,res.sum就是所有y的和,tmp.sum就是所有x的和,等式两边同时求和,不难发现 i * (10^pos)这一项就要出现tmp.cnt次,所以,res.sum = i * (10^pos) * tmp.cnt + tmp.sum;
同理求解平方和,x与y的定义如上,对于平方和可以有这样的关系 y^2 = (i * (10^pos) +x)^2,拆开来看,就是y^2 = (i*(10^pos))^2 + 2*(i*(10^pos))*x + x^2,同样等式左右两边求和,y^2求和就是res.sqsum,x^2求和就是tmp.sqsum,(i*(10^pos))^2对于每个x都要出现一次,所以这一项需要出现tmp.cnt次,所以整理可得,res.sqsum = (i*(10^pos))^2*tmp.cnt + 2 * (i*(10^pos))^2 * tmp.sum + tmp.sqsum
理解清楚思路后代码很简单,注意模运算。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;

struct node {
    ll cnt, sum, sqsum;
}dp[20][10][10];

ll ten[20];
int a[20];

void init() {
    ten[0] = 1;
    for (int i = 1; i < 20; i++)
        ten[i] = ten[i - 1] * 10 % mod;
    memset(dp, -1, sizeof(dp));
}

node dfs(int pos, int pre_sum, int pre_mod, bool limit) {
    if (pos == -1) {
        if (pre_sum == 0 || pre_mod == 0) return (node){0, 0, 0};
        return (node){1, 0, 0};
    }
    if (!limit && dp[pos][pre_sum][pre_mod].cnt != -1) return dp[pos][pre_sum][pre_mod];
    int up = limit ? a[pos] : 9;
    node res = (node){0, 0, 0};
    for (int i = 0; i <= up; i++) {
        if (i == 7) continue;
        node tmp = dfs(pos - 1, (pre_sum + i) % 7, (pre_mod * 10 + i) % 7, limit && a[pos] == i);
        res.cnt += tmp.cnt;
        res.cnt %= mod;
        res.sum += (tmp.sum + ((ten[pos] * i) % mod) * tmp.cnt % mod) % mod;
        res.sum %= mod;
        res.sqsum += (tmp.sqsum + ((2 * ten[pos] * i) % mod) * tmp.sum) % mod;
        res.sqsum %= mod;
        res.sqsum += (tmp.cnt * ten[pos]) % mod * ten[pos] % mod * i * i % mod;
        res.sqsum %= mod;
    }
    if (!limit) dp[pos][pre_sum][pre_mod] = res;
    return res;
}

ll solve (ll x) {
    int pos = 0;
    while (x) {
        a[pos++] = x % 10;
        x /= 10;
    }
    return dfs(pos - 1, 0, 0, true).sqsum;
}

int main() {
    init();
    int T;
    scanf("%d", &T);
    while (T--) {
        ll n, m;
        scanf("%I64d%I64d", &n, &m);
        printf("%I64d\n", ((solve(m) - solve(n - 1)) % mod + mod) % mod);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值