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

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

题意简述

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

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

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

数据

输入

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

输出

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

样例

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

思路

写数位DPDP最容易的事情就是写挂,真的。。。

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

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

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

然后这个dpdp是干啥的呢?用来记忆化搜索的。搜索时候的状态,也就是长度,位数和余数,原数余数。然后我们现在设长度是pospos,当前已经确定的位(有cntpos+1cnt-pos+1位)的数字和膜77statestate,当前已经确定的数膜77的余数rr

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

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

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

边界就是我们枚举的位数长度到了00。如果两个余数都不为00,那么我们有一个解,就是00。此时cnt=1,s1=0,s2=0cnt=1,s1=0,s2=0。否则就是一个解也没有,cnt=s1=s2=0cnt=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;
}

回到总题解界面

发布了217 篇原创文章 · 获赞 8 · 访问量 9859
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览