HDU4507吉哥系列故事——恨7不成妻(数位dp)

题目链接: 点击打开链接
题意:
求区间[l,r]内所有与7无关的数的平方和(取模)
定义与7无关的数: 
                                     1.数字的数位上不能有7
                                     2.数字的数位和不能是7的倍数
                                     3.数字本身不能是7的倍数
分析:
状态的保存:
1.数位上不能有7: 只需枚举数位的数字的时候跳过7就好 if (i == 7) continue;
2.数位和不能是7的倍数: 那么开一维保存数位和除以7的余数
3.数字本身不能是7的倍数:再开一维保存数字除以7的余数
综上,dp[i][j][k]3个维度保存的数字属性分别是:
                           i : 当前处理的数位
                           j : 数位和%7 等于j
                           k: 数字本身%7等于k
对于具有上述属性的数,用dp保存它们的3个值:(用结构体)
                         cnt: 具有该属性的所有数字的个数
                         sum :具有该属性的所有数字的和
                         sqsum:具有该属性的所有数字的平方和
为什么要保存这3个值?为了下面的计算
状态的转移:

关于状态转移,先简单的写这样一个式子:dp[i][j][k] = ∑dp[i-1][(j+dig)%7][(k*10+dig)%7](这里的求和符号不指加法,是一个抽象的意义)

其中dig是枚举的正在处理的数位i上所有可能的数字(这个式子只能帮助理解状态是如何转移的但是却不表示具体的运算,dp是结构体当然不能直接运算)

上面的等式,我们称等式左边表示总状态,等式右边为其子状态,显然总状态是等于所有子状态的“总和”(我说的状态的总和并非指加法运算)

那么怎么通过子状态算出总状态呢?

具体的计算:

先说几句废话:

对于13这个数,它的数位上的数是1,3,它的数位和是1+3,它自身的数值是 1*10+3

如果我知道数字13是与7无关的数,在其前面加一个1,也是与7无关的数,怎样计算在其前面加一个1之后的数的平方和的呢

(1*100)^2 + 2*(1*100)*13 + 13^2  这里相当于(100+13)^2

注意:在具体的状态转移中,我们是不知道13这个值,我们只知道有这么一个子状态


要求的是所有满足的数的平方和,所以最后具体的算式如下:

设总状态(当前状态)为ret,它其中一个子状态为nxt,枚举正在处理的这一数位上的数字为 i ,数位 i 在整个数字中具体的数值是i*10^pw10[pos]

那么有: 

                 (1) ret.cnt += ret.cntnxt.cnt//这个与求个数的那类题型一样

                 (2) ret.sum += nxt.sum + i*10^pw10[pos]*nxt.cnt    //之所以*数量,是因为后面有许多个符合条件的数,这一位可以跟后面所有符合条件的数搭配    

                 (3) ret.sqsum += nxt.sqsum + 2*(i*10^pw10[pos])*nxt.sum + [(i*10^pw10[pos])^2]*nxt.cnt//这个是最难理解的式子,也就是是上面的完全平方式子,因为当前位固定,后面有很多满足情况的数位,所以与单独一个1配上后面13有点不一样,此处需要理解10分钟



ps:题目中需要大量的取余,还有,因为取余的原因,ansr - ansl可能结果为负,所以要加上mod再取余。
负数取余为(ans + mod) % mod,还有就是在计算10的多少次幂都可以提前取余,这是不影响计算的。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const ll mod = (ll)(1e9+7);
int digit[20], vis[20][10][10];
ll pw10[20];
struct Node
{
    ll cnt, sum, sqsum;
    Node(){cnt = -1; sum = 0; sqsum = 0;}
    Node(ll _cnt, ll _sum, ll _sqsum): cnt(_cnt), sum(_sum), sqsum(_sqsum){}
}dp[20][10][10];
Node dfs(int pos, int sum_rem, int num_rem, bool limit)
{
    if(pos == -1)
    {
        if(sum_rem == 0 || num_rem == 0)
            return Node{0, 0 , 0};
        else
            return Node{1, 0, 0};
    }
    if(!limit && dp[pos][sum_rem][num_rem].cnt != -1)
        return dp[pos][sum_rem][num_rem];
    int last = limit ? digit[pos] : 9;
    Node ret = Node{0, 0, 0};
    for(int i = 0; i <= last; i++)
    {
        if(i == 7)
            continue;
        Node nxt = dfs(pos - 1, (sum_rem + i) % 7, (num_rem * 10 + i) % 7, limit && (i == last));
        ret.cnt = (ret.cnt + nxt.cnt) % mod;
        ret.sum = ((ret.sum + pw10[pos] * i % mod * nxt.cnt % mod) % mod + nxt.sum) % mod;
        ret.sqsum = (ret.sqsum + nxt.sqsum) % mod;
        ret.sqsum = (ret.sqsum + pw10[pos] * pw10[pos] % mod * i * i % mod * nxt.cnt % mod) % mod;
        ret.sqsum = (ret.sqsum + 2 * pw10[pos] * i % mod * nxt.sum % mod) % mod;
    }
    if(!limit)
        dp[pos][sum_rem][num_rem] = ret;
    return ret;
}
ll solve(ll x)
{
    memset(digit, 0, sizeof(digit));
    int len = 0;
    while(x)
    {
        digit[len++] = x % 10;
        x /= 10;
    }
    return dfs(len-1, 0, 0, true).sqsum;
}
int main()
{
    int T;
    ll L, R;
    pw10[0] = 1;
    for(int i = 1; i <= 18; i++)
        pw10[i] = pw10[i-1] * 10 % mod;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%I64d%I64d", &L, &R);
        ll ans = (solve(R) - solve(L-1)) % mod;
        printf("%I64d\n", (ans + mod) % mod);
    }
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值