数位dp-HDU-4507-恨7不成妻-区间满足条件和平方和

题目链接:https://vjudge.net/contest/308832#problem/G
题目大意:题目大意:求指定范围内与7不沾边的所有数的平方和。结果要mod 10^9+7。
转大佬:https://www.cnblogs.com/neopenx/p/4008921.html
与7不沾边的数需要满足三个条件。
①不出现7
②各位数和不是7的倍数
③这个数不是7的倍数

但是这题要统计的不是符合条件个数,而是平方和。

也就是说在DP时候,要重建每个数,算出平方,然后求和。

需要维护三个值(推荐使用结构体), 假定dfs推出返回的结构体是next,当前结果的结构体是ans。

其中①是基础数位DP。
②next.sum+(10 ^ len * i) * ans.cnt
③首先重建一下这个数,(10^len * i + x),其中x是这个数的后面部分,则平方和就是(10^len* i)^2+x ^ 2+2 * 10^len * i * x,其中x^2=next.sqsum
整体还要乘以next.cnt,毕竟不止一个。

这样sqsum+=next.sqsum

sqsum+=(2 * i * 10^len) * next.sum(神奇的化简)

sqsum+=(i * 10 ^ len ) ^ 2 * next.cnt

这个公式可以推一下:就是一个平方项展开。
例如:12
根据数位dp, a=10, b=2
那么(a+b) ^ 2 = a ^ 2 + b ^ 2 + 2 * a * b

如果有n个:powsum = (a+b1)^2 + (a+b2)^2 + … + (a+bn)^2 = n * a ^2 +n * b ^ 2 +2* n* a* (b1+b2+…+bn)

所有统计cut(n), sum(Σbi) powsum(所有bi的平方和),就可以维护了。

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

int a[20];
LL pow10[20];
struct node
{
    LL cut;
    LL sum;
    LL powsum;
    int f;
    node(){cut=0, sum=0, powsum=0, f=0;}
}dp[20][10][10];

node dfs(int pos, LL md, LL s, int mt)
{
    if(pos==-1)
    {
        node now;
        if(md==0||s==0)
        {
            now.f=1;
            return now;
        }
        else
        {
            now.cut=1, now.sum=0, now.powsum=0, now.f=1;
            return now;
        }
    }
    if(!mt&&dp[pos][md][s].f)
    {
        return dp[pos][md][s];
    }

    int Len=mt?a[pos]:9;

    node ans;
    for(LL i=0;i<=Len;i++)
    {
        if(i==7)
        {
            continue;
        }

        node now=dfs(pos-1, (md*10+i)%7, (s+i)%7, mt&&i==a[pos]);

        ans.cut=(ans.cut+now.cut)%mod, ans.sum=((ans.sum+(((i*pow10[pos])%mod)*now.cut)%mod)%mod+now.sum)%mod;

        ans.powsum=(ans.powsum+(((i*pow10[pos]%mod)*(i*pow10[pos]%mod))%mod*now.cut)%mod+now.powsum+((2*((i*pow10[pos]%mod)*now.sum)%mod)%mod)%mod)%mod;

        ans.f=1;
    }

    if(!mt)
    {
        dp[pos][md][s]=ans;
    }

    return ans;
}

LL slove(LL x)
{
    int pos=0;
    while(x)
    {
        a[pos++]=x%10;
        x/=10;
    }

    return (dfs(pos-1, 0, 0, 1).powsum)%mod;
}

int main()
{
    pow10[0]=1;
    for(int i=1;i<20;i++)
    {
        pow10[i]=pow10[i-1]*10;
    }
    int T;
    scanf("%d",&T);
    while(T--)
    {
        LL L, R;
        scanf("%lld%lld",&L,&R);

        printf("%lld\n", (slove(R)-slove(L-1)+mod)%mod);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值