数位dp好题-恨7不成妻

2023大厂真题提交网址(含题解):

www.CodeFun2000.com(http://101.43.147.120/)

最近我们一直在将收集到的机试真题制作数据并搬运到自己的OJ上,供大家免费练习,体会真题难度。现在OJ已录入50+道2023年最新大厂真题,同时在不断的更新。同时,可以关注"塔子哥学算法"公众号获得每道题的题解。
在这里插入图片描述

题目大意:

题目地址

让你求出 [ l , r ] [l,r] [l,r]中满足①数位中不含7的数②数位之和不是7的倍数③数本身不是7的倍数 的所有数的平方和.

题目思路:

这个题算是一个数位dp好题了。他让我对数位dp的过程有了一个更深刻的看法。

其他很简单,难点在于平方和.先将数位映射到树上

1.以往让我们求区间合法的数的个数 等同于 求树上叶子节点个数
2.现在要求区间 [ 1 , n ] [1,n] [1,n]的每个数的和,如何做?

还是映射到树上。因为我们数位dp的本质就是将每个数按位拆分,而且每个数位状态代表树上一个节点,即任意一个 d p ( i , j , k ) dp(i,j,k) dp(i,j,k)代表以树上该节点为根的子树的答案/贡献.然后考虑转移。

假设某状态 ( i , j ) (i,j) (i,j)代表前i位,是否顶到上界,该节点放置数位 d d d.要求它的贡献。那该颗子树所代表的数的个数即为叶子节点个数.假设为 k k k.再假设这些数是: { x 1 , x 2 , . . . , x k } \{x_1,x_2,...,x_k\} {x1,x2,...,xk}.转移就是在每个数前面(最高位)要添加一个数位 i i i.那最终答案(dp值)为: ( x 1 + d ∗ 1 0 p ) + ( x 2 + d ∗ 1 0 p ) + . . . + ( x k + d ∗ 1 0 p ) (x_1+d*10^p)+(x_2+d*10^p)+...+(x_k+d*10^p) (x1+d10p)+(x2+d10p)+...+(xk+d10p)

稍微变形可得: ∑ i = 1 k x i + ∑ i = 1 k d ∗ 1 0 p \sum_{i=1}^k x_i+\sum_{i=1}^k d*10^p i=1kxi+i=1kd10p.

前半部分就是下一个阶段的 d p dp dp值,后半部分的 k k k(即合法数的个数,即叶子节点个数)我们需要额外开一个数组 g g g去维护.

3.现在要求区间 [ 1 , n ] [1,n] [1,n]的每个数的平方和,如何做?

分析套路和上面一模一样,假设平方和答案为 f f f数组。那么最终答案为 ( x 1 + d ∗ 1 0 p ) 2 + ( x 2 + d ∗ 1 0 p ) 2 + . . . + ( x k + d ∗ 1 0 p ) 2 (x_1+d*10^p)^2+(x_2+d*10^p)^2+...+(x_k+d*10^p)^2 (x1+d10p)2+(x2+d10p)2+...+(xk+d10p)2

稍微变形可得:

原式 = ∑ i = 1 k ( x i 2 + d 2 1 0 2 p + 2 ∗ x i ∗ d ∗ 1 0 p ) 原式=\sum_{i=1}^k (x_i^2+d^210^{2p}+2*x_i*d*10^p) 原式=i=1k(xi2+d2102p+2xid10p)
= f ( i − 1 ) + g ( i − 1 ) ∗ d 2 1 0 2 p + 2 ∗ d ∗ 1 0 p ∗ d p ( i − 1 ) =f(i - 1)+g(i-1)*d^210^{2p}+2*d*10^p*dp(i-1) =f(i1)+g(i1)d2102p+2d10pdp(i1)

所以这个部分维护 d p , g , f dp,g,f dp,g,f分别代表合法数的个数,累和,平方和即可.

心得:

分析数位dp问题的时候脑子里装一颗树即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vl vector<ll>
#define fi first
#define se second
const int maxn = 1e6 + 5;
const int mx = 7;
const int mod = 1e9 + 7;
string xx;
ll p[55] , p2[55];
struct Node
{
    ll num , sum , ssum; // 个数 , 和 , 平方和
}dp[20][2][8][8];
Node dfs (int step , int lim , int pre1 , int pre2)
{
    if (step == -1) return {pre1 && pre2 , 0 , 0};
    Node &x = dp[step][lim][pre1][pre2];
    if ( ~x.num ) return x;
    int up = lim ? xx[step] - '0' : 9;
    Node ans = {0,0,0};
    for (ll i = 0 ; i <= up ; i++){
        // ① 数位中不能出现7
        if (i == 7) continue;
        Node tmp = dfs(step - 1 , lim && i == up , (pre1 + i)%mx, (pre2 + i * p2[step]%mx)%mx);
        ans.num = (ans.num + tmp.num) % mod;
        ans.sum = (ans.sum + tmp.sum) % mod; // 来自子树的贡献
        ans.sum = (ans.sum + i * p[step]%mod * tmp.num%mod); // 节点的贡献

        // 三个部分
        ans.ssum = (ans.ssum + i * i%mod * p[2 * step]%mod * tmp.num %mod)%mod;
        ans.ssum = (ans.ssum + tmp.ssum)%mod;
        ans.ssum = (ans.ssum + 2 * i % mod * p[step] % mod * tmp.sum %mod)%mod;
    }
    return x = ans;
}
ll solve (ll x)
{
    memset(dp , -1 , sizeof dp);
    xx = to_string(x);
    reverse(xx.begin() , xx.end());
    return dfs(xx.size() - 1 , true , 0 , 0).ssum;
}
int main()
{
    ios::sync_with_stdio(false);
    int t; cin >> t;
    p[0] = p2[0] = 1;
    for (int i = 1 ; i <= 50 ; i++){
        p[i] = p[i - 1] * 10 % mod;
        p2[i] = p2[i - 1] * 10 % mx;
    }
    while (t--){
        ll l , r; cin >> l >> r;
        cout << (solve(r) - solve(l - 1) + mod)%mod << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值