hdu 4507 吉哥系列故事——恨7不成妻 题解(数位DP(超难))

博客详细介绍了如何使用数位动态规划解决HDU 4507题目,即求解在给定区间内与7无关的正整数的平方和。博客内容包括题意解析、数据描述、解题思路及关键转移方程,并给出了代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接:
HDU:点我QωQ

题意简述

定义一个正整数和 7 7 7有关,满足以下三个之一:

  1. 某一位是 7 7 7
  2. 数位和是 7 7 7的倍数
  3. 原数是 7 7 7的倍数

那么就和 7 7 7有关。给定 Q Q Q Q Q Q个区间 [ l , r ] [l,r] [l,r],请求出 l , r l,r l,r里面和 7 7 7无关的数的平方和。

数据

输入

第一行是一个 Q ( Q &lt; = 50 ) Q(Q&lt;=50) Q(Q<=50)
接下来 Q Q Q行每行两个正整数 l , r ( 1 &lt; = l , r &lt; = 1 e 18 ) l,r(1&lt;=l,r&lt;=1e18) l,r(1<=l,r<=1e18)

输出

对于每个询问,输出答案。

样例

输入
3
1 9
10 11
17 17
输出
236
221
0

思路

写数位 D P DP DP最容易的事情就是写挂,真的。。。

我们先来设 d p dp dp状态。一开始我状态设多了,导致我转移都不会转移。。。来考虑几个状态,我们看看要不要:

  1. 位数:这个显然要用
  2. 首位:重要么?这个题中,如果我们要找和 7 7 7无关的,那么数位中肯定没有 7 7 7。在数位方面,我们只要保证这一点即可。为了保证这一点,我们只要在循环的时候不去搜 7 7 7即可。所以首位不用存。
  3. 有没有 7 7 7:同理,也不用。
  4. 位数和膜 7 7 7的余数:显然需要
  5. 原数膜 7 7 7的余数:同理,需要。

所以就是三维, d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示长度为 i i i,位数和余数是 j j j,原数余数是 k k k
当然,一个 d p dp dp的答案还不能就是一个数:平方和。我们在推转移式子的时候,我们会发现:我们也要维护好满足条件的个数和一次方和。这个也不是不能维护,所以我们考虑打个结构体,都维护上。

然后这个 d p dp dp是干啥的呢?用来记忆化搜索的。搜索时候的状态,也就是长度,位数和余数,原数余数。然后我们现在设长度是 p o s pos pos,当前已经确定的位(有 c n t − p o s + 1 cnt-pos+1 cntpos+1位)的数字和膜 7 7 7 s t a t e state state,当前已经确定的数膜 7 7 7的余数 r r r

然后,转移的时候这么转移:

  1. 枚举下一位 i i i(不能是 7 7 7
  2. 此时由于我们多确定了一位,位数和便加上了我们枚举的新的位 i i i,当然,确定的数和便先 ∗ 10 *10 10,再加上了 i i i。(和快读的思想类似)
  3. 继承一些值。
设当前答案是 a n s ans ans,子状态的答案是 t m p tmp tmp。这个答案是一个结构体,里面包含个数 c n t cnt cnt,一次方和 s 1 s1 s1,二次方和 s 2 s2 s2
显然 a n s . c n t + = t m p . c n t ans.cnt+=tmp.cnt ans.cnt+=tmp.cnt(此处省略取膜)
我们考虑一下, t m p tmp tmp里面的和是 s 1 s1 s1,现在相当于 t m p tmp tmp中每个数都加上了最高位的权(设为 h i g h high high,即 i ∗ 1 0 p o s − 1 i*10^{pos-1} i10pos1),所以就加上了 t m p . c n t tmp.cnt tmp.cnt h i g h high high。即 a n s . s 1 + = t m p . s 1 + h i g h ∗ t m p . c n t ans.s1+=tmp.s1+high*tmp.cnt ans.s1+=tmp.s1+hightmp.cnt
继续推式子。当然,这个是本题中最毒瘤的式子。 t m p tmp tmp里面的平方和是 s 2 s2 s2,设这个值是 x 1 2 + x 2 2 . . . + x c n t 2 x_1^2+x_2^2...+x_{cnt}^2 x12+x22...+xcnt2,其中 x 1 , x 2 . . . x c n t x_1,x_2...x_{cnt} x1,x2...xcnt t m p tmp tmp中那些和 7 7 7无关的数, c n t cnt cnt也就是 t m p . c n t tmp.cnt tmp.cnt。那么,每个数都加上了 h i g h high high,就变成:
( x 1 + h i g h ) 2 + ( x 2 + h i g h ) 2 . . . + ( x c n t + h i g h ) 2 (x_1+high)^2+(x_2+high)^2...+(x^{cnt}+high)^2 (x1+high)2+(x2+high)2...+(xcnt+high)2
= ( x 1 2 + x 2 2 . . . + x k 2 ) + 2 x 1 h i g h + 2 x 2 h i g h . . . + 2 x c n t h i g h + c n t ∗ h i g h 2 =(x_1^2+x_2^2...+x_k^2)+2x_1high+2x_2high...+2x_{cnt}high+cnt*high^2 =(x12+x22...+xk2)+2x1high+2x2high...+2xcnthigh+cnthigh2
= t m p . s 2 + 2 h i g h ∗ t m p . x 1 + h i g h 2 ∗ t m p . c n t =tmp.s2+2high*tmp.x1+high^2*tmp.cnt =tmp.s2+2hightmp.x1+high2tmp.cnt
这下就珂以算了。为了避免式子过于长,我们把后面两个(出来 t m p . s 2 tmp.s2 tmp.s2那两个)分成两个变量计算。

还有一点要注意,我们的数位 D P DP DP本质是记忆化搜索,但并不是所有时候都能记忆化的。有一种特殊情况,就是前缀的情况。举个栗子,我们要算 1 1 1 123546 123546 123546的和,那么我们在枚举到 1235 ∗ ∗ 1235** 1235 ∗ ∗ ** 表示还没确定)的时候,就需要特殊判断,不能直接继承答案。这个能不能继承,我们用一个参数 l i m lim lim表示。这个 l i m lim lim的传递么。。。就是每次枚举到和当前位相同的时候传递为真,否则是假。

边界就是我们枚举的位数长度到了 0 0 0。如果两个余数都不为 0 0 0,那么我们有一个解,就是 0 0 0。此时 c n t = 1 , s 1 = 0 , s 2 = 0 cnt=1,s1=0,s2=0 cnt=1,s1=0,s2=0。否则就是一个解也没有, c n t = s 1 = s 2 = 0 cnt=s1=s2=0 cnt=s1=s2=0

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    #define N 25
    #define mod 1000000007
    struct node
    {
        int cnt;
        int s1,s2;
    }dp[N][7][7];
    int p10[N];
    int d[N],cnt;
    node DFS(int pos,bool lim,int state,int r)
    {
        if (pos==0)//边界
        {
            if (r!=0 and state!=0)
            {
                return (node){1,0,0};
            }
            else
            {
                return (node){0,0,0};
            }
        }
        if (!lim and dp[pos][state][r].s1!=0)//珂以继承的情况:
        //1. 不是前缀
        //2. 答案算过
        //两个必须都成立才珂以继承
        {
            return dp[pos][state][r];
        }

        int up=lim?d[pos]:9;
        //如果当前是前缀,就只能取到d[pos]了,多了就超过了x。
        //如果不是前缀,说明:
        //1. 长度和x一样,某一位比x小
        //2. 位数比x小
        //反正这两种情况,在枚举的时候,后面的位都是没有限制的,因为他们"输在了起跑线(前面的位)上"
        node ans;ans=(node){0,0,0};
        for(int i=0;i<=up;++i)
        {
            if (i==7) continue;
            int s=(state+i)%7,m=(r*10%7+i)%7;
            //多确定一位i,所以是加上去的
            //(这个东西我开始的时候居然理解不了。。。我现在感觉我当时是智障)

            node tmp=DFS(pos-1,(lim&&i==up),s,m);
            //当且仅当我们现在是前缀
            //并且下一位也是和x一样时
            //lim继续为1
            //否则是0
            int high=i*p10[pos-1]%mod;
            //新确定的这一位的权值
            ans.cnt+=tmp.cnt;ans.cnt%=mod;
            ans.s1+=(tmp.s1+high*tmp.cnt%mod);ans.s1%=mod;
            int k1=high*high%mod*tmp.cnt%mod;
            int k2=2*high%mod*tmp.s1%mod;
            ans.s2+=((k1+k2)%mod+tmp.s2%mod);ans.s2%=mod;
            //刚才的方程+老多老多的%
        }
        if (!lim) dp[pos][state][r]=ans;
        return ans;
    }
    int calc(int x)
    {
        cnt=0;memset(d,0,sizeof(d));
        while(x)
        {
            d[++cnt]=x%10;
            x/=10;
        }//分解位

        node tmp=DFS(cnt,1ll,0ll,0ll);
        return tmp.s2%mod;//计算答案
    }

    void Init()
    {
        for(int i=0;i<=18;++i)
        {
            p10[i]=(i==0)?1:p10[i-1]*10;
            p10[i]%=mod;
        }//打表快速幂
    }
    void Main()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Init();
        int t;scanf("%lld",&t);
        for(int i=1;i<=t;++i)
        {
            int l,r;scanf("%lld%lld",&l,&r);
            printf("%lld\n",(calc(r)-calc(l-1)+mod)%mod);
        }
    }
    #undef N //25
    #undef int //long long
    #undef mod //1000000007
};
int main()
{
    Flandle_Scarlet::Main();
    return 0;
}

回到总题解界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值