hdu4507数位dp公式推导详解

题目地址
这道题看了很多的题解才看明白,一开始纠结的不是看不懂他们的题解,是认为自己的没有错但是确实错的,这很难受,不过懂了以后就明白过来其实都是没有真正理解到。
题目的大意就是从[l,r]上选出和7无关的数的平方和,如果是求个数的话就是一道很简单的dp了,和7有关被定义为以下3种情况:
 1、整数中某一位是7;
2、整数的每一位加起来的和是7的整数倍;
 3、这个整数是7的整数倍;
如果之前已经对数位dp有基础的,就能直接想到条件1可以直接在递归的时候处理,没必要加入dp的状态里。这里和其他人一样,很容易想到定义3维dp,dp[i][sum][res]表示长度为i,和为sum,余数为res的情况。那么我们首先来想想,这个dp里要存哪些数据呢?也就是假如我们当前知道长度为i的和7无关的数的平方和为t,怎么推导到长度为i + 1的平方和呢?比如当前一个和7无关的数为16,但是对于i+1应该是416,那么416²=(16 + 400)² = 400² + 2 * 16 * 400 + 16 * 16。也就是对于i-1长度里的一个和7无关的数a,要推导到i+1长度,就应该y=10的i次方 * 当前最高位的数(比如416就是4),那么(y + a) ² = y² + 2ay + a²。但是我们现在不止一个数a,有很多个数,那么就应该是在这里插入图片描述
所以y²我们可以在当前循环得到,不需要记录。
2ay种把2y提出来,那么其实也就是要得到所有和7无关的数的和
a²,指的就是i-1长度的和7无关的数的平方和
所以就明确了,我们需要记录和7无关的个数cnt,和7无关的数的和sum,和7无关的数的平方和square_sum。
并且可以得到推导公式:
p[i]等于10的i次方,这里用i-1,比如17,i = 2,就应该是7 + p[1],所以是i-1
d指的是最高位的数字,比如35就是3
y = d * p[i - 1]
ans.cnt += tmp.cnt;
ans.sum += tmp.sum + y * tmp.cnt;
ans.s_sum += tmp.s_sum + 2 * y * tmp.sum + y * y * tmp.cnt
这里要注意几个事项,由于要取模,所以取模要多加,防止溢出,还有最后的solve® - solve(l - 1),由于取模了,所以可能是负的,需要先加mod要取模。
最后贴上ac代码:

#include <iostream>
#include <cstring>

using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int LEN = 20;
struct node{
    ll cnt, sum, s_sum;
    node() {
        cnt = -1; sum = 0; s_sum = 0;
    }
    node (int a1, int a2, int a3) {
        cnt = a1; sum = a2; s_sum = a3;
    }
}dp[LEN][10][10];
ll p[LEN], digit[LEN];
void init() {
    p[0] = 1;
    for (int i = 1; i < LEN; i++)
        p[i] = p[i - 1] * 10 % MOD;
}

node dfs(int i, int sum, int res, bool e) {
    if (!i) return node(res&&sum, 0, 0);
    if (!e && ~dp[i][sum][res].cnt) return dp[i][sum][res];
    node ans;
    ans.cnt = 0;
    int u = e?digit[i]:9;
    for (int d = 0; d <= u; ++d) {
        if (d == 7) continue;
        node tmp = dfs(i - 1, (sum + d) % 7, (res * 10 + d) % 7, e&&d==u);
        ll y = p[i - 1] * d % MOD;
        ans.cnt  = (ans.cnt + tmp.cnt) % MOD;
        ans.sum = (ans.sum + tmp.sum + tmp.cnt * y % MOD) % MOD;
        ans.s_sum = (ans.s_sum + tmp.cnt * y % MOD * y % MOD) % MOD;
        ans.s_sum = (ans.s_sum + 2 * y % MOD * tmp.sum + tmp.s_sum) % MOD;
    }
    return e?ans:dp[i][sum][res] = ans;
}

int solve(ll n) {
    int len = 0;
    while(n) {
        digit[++len] = n % 10;
        n /= 10;
    }
    return dfs(len, 0, 0, true).s_sum % MOD;
}

int main()
{
    int T;
    scanf("%d", &T);
    init();
    while(T--) {
        ll l, r; scanf("%lld %lld", &l, &r);
        printf("%d\n", (solve(r) - solve(l - 1) + MOD) % MOD);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值