HDU ~ 4507 ~ 吉哥系列故事――恨7不成妻 (数位DP + 完全平方公式)

思路

依旧是数位DP, 我们需要过程中需要维护数字num%7和每一位的和sum%7。

所以定义DP[pos][mod1][mod2]表示选到pos位置num%7=mod1和sum%7=mod2的答案。

如果答案是要数字个数就好办了,但是答案要的是平方和。如果第二位我们去存当前选的数字num,而不是num%7,我们内存会炸,不可行。

大家应该还记得完全平方公式吧,即(a+b)^2=a^2+2ab+b^2,假设要求一个两位数x的答案,假设第一位为pre,第二位为t,那么x = pre*10 + t,所有符合要求的 x 的平方和怎么求呢?首先第一位选可行数字 i 的情况下,第二位选一个可行的值 t ,答案就需要加上此时选的数平方即(i*10+t)^2=i^2*10^2+2*i*10*t +t^2。如果对于 i 的情况有多个可行的 t

,那么答案就是cnt\cdot(i^2*10^2)+2*i*10*\sum t+\sum t^2。推广到选多位数字的时候一样。

DP数组需要维护三个值,cnt 表示当前状态下后面随便选的可行数字个数,sum表示后面位的 \sum t,sqsum表示 \sum t^2

假设选到pos位置,当前选了 i 元素,深搜得到的dp值为 t 。

cnt 的维护比较简单,一路加过来就好了,ans.cnt \quad += \quad t.cnt

sum的维护,ans.sum \quad += \quad t.sum + t.cnt*i*10^{pos}

sqsum(平方和)的维护,ans.sqsum += t.cnt*(i*10^{pos})^2+2* i*10^{pos}* t.sum+t.sqsum

然后注意下mod和long long即可。

因为取模的原因,最后R -(L-1)可能为负数所以要+mod在取余。

 

#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9+7;
typedef long long LL;
struct DP
{
    LL cnt, sum, sqsum;
    DP () {}
    DP (LL cnt, LL sum, LL sqsum): cnt(cnt), sum(sum), sqsum(sqsum) {}
}dp[20][10][10];
LL ten[20];
string s;
DP dfs(int pos, int mod1, int mod2, bool limit)
{
    if (pos == -1) return DP(mod1!=0 && mod2!=0, 0, 0);
    if (!limit && dp[pos][mod1][mod2].cnt != -1) return dp[pos][mod1][mod2];
    int E = limit?s[pos]-'0':9;
    DP ans = DP(0, 0, 0);
    for (int i = 0; i <= E; i++)
    {
        if (i == 7) continue;
        DP t = dfs(pos-1, (mod1+i)%7, (mod2*10+i)%7, limit&&(i==E));
        ans.cnt += t.cnt;
        ans.cnt %= MOD;

        ans.sum += t.sum + t.cnt*i%MOD*ten[pos]%MOD;
        ans.sum %= MOD;

        ans.sqsum += t.cnt*i*i%MOD*ten[pos]%MOD*ten[pos]%MOD;
        ans.sqsum += t.sqsum + 2*i*ten[pos]%MOD*t.sum%MOD;
        ans.sqsum %= MOD;
    }
    if (!limit) dp[pos][mod1][mod2] = ans;
    return ans;
}
LL solve(LL x)
{
    s = to_string(x); reverse(s.begin(), s.end());
    return dfs(s.size()-1, 0, 0, 1).sqsum;
}
int main()
{
    ten[0] = 1;
    for (int i = 1; i < 20; i++) ten[i] = (ten[i-1]*10)%MOD;
    memset(dp, -1, sizeof(dp));
    int T; scanf("%d", &T);
    while (T--)
    {
        LL L, R; scanf("%lld%lld", &L, &R);
        printf("%lld\n", (solve(R)-solve(L-1)+MOD)%MOD);
    }
    return 0;
}
/*
3
1 9
10 11
17 17
*/

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值