1590:恨 7 不成妻(数位DP)

这篇博客探讨了一个使用动态规划求解区间内所有与7无关的数的平方和的问题。作者详细解释了如何进行数学转换,通过固定最高位并枚举次高位来递推,同时提到了在处理过程中模运算的注意事项。代码实现中包含了预处理步骤和求平方和的公式,并给出了完整的C++代码示例。

这题有一个难点一个坑点,and 你要细点

难点

你要用DP维护出区间内所有和7无关的数的平方和。

这个需要你做一个数学转换。

因为你用DP预处理一个区间的所有与7无关的数,就是通过固定最高位,枚举次高位来递推。所以这个时候我们
可以试着把一个数拆成最高位和剩余位的和,看看它有没有什么特点,从而得到DP的状态转移方程。

所以一个数可以拆成 t∗10k+xi t*10^{k} + x_{i} t10k+xi

那么可以固定最高位,枚举低位,得到每个数的平方和如下

(x1+t∗10k)2+(x2+t∗10k)2+⋯(xi+t∗10k)2+⋯+(xn+t∗10k)2 \left ( x_{1} + t*10^{k}\right )^{2} + \left ( x_{2} + t*10^{k}\right )^{2} +\cdots \left ( x_{i} + t*10^{k}\right )^{2} + \cdots + \left ( x_{n} + t*10^{k}\right )^{2} (x1+t10k)2+(x2+t10k)2+(xi+t10k)2++(xn+t10k)2

整理下
n∗(t∗10k)2+2(t∗10k)∗(x1+x2+⋯+xi+⋯+xn)+(x12+x22+⋯+xi2+⋯+xn2)n * (t * 10^{k})^{2} + 2(t * 10^{k}) * (x_{1} + x_{2} +\cdots + x_{i} + \cdots + x_{n}) + (x_{1}^2 + x_{2}^2 + \cdots + x_{i}^2 + \cdots + x_{n}^2)n(t10k)2+2(t10k)(x1+x2++xi++xn)+(x12+x22++xi2++xn2)

再整理下得到
n∗(t∗10k)2+2(t∗10k)∗∑xi+∑xi2n * (t * 10^{k})^{2} + 2(t * 10^{k}) * \sum x_i + \sum x_i^2n(t10k)2+2(t10k)xi+xi2

n:合法低位的的数量 n :合法低位的的数量 n
∑xi:合法低位的和 \sum x_i :合法低位的和xi:
∑xi2:合法低位的平方和 \sum x_i^2 :合法低位的平方和xi2:

所以你要维护上面的3个DP数组。求平方和的时候用下以上公式求就行



坑点

处理DP过程以及求平方和过程需要用到

%7 \% 7 %7
%(109+7) \%(10^{9} + 7 )%(109+7)

但一个数模一个数后再模另一个数答案会有影响

比如 10 % 3 = 1.
但10先%5了 那么10 == 0
0 % 3 = 0

所以一定要提前各自预处理 10^k 对 7 和对 (10 ^9 + 7 )的结果,不要用混了



code

这个代码有auto, 所以信奥里不能编译,HDU用G++可以过

#include <bits/stdc++.h>
using namespace std;
const int N = 22;
typedef long long ll;

struct ty
{
    ll s0, s1, s2;// 低位个数,低位和,低位平方和
} f[N][N][7][7];// 处理到第i位,第i位位j,各位和%7 为 l 的数,数本身%7 为 m 的数

ll key9[N];// 10^k % 1e9 + 7
ll key7[N];// 10^k % 7

ll b[N];//存这个数的各个位
const ll mod = 1e9 + 7;

void init()
{
    key9[1] = key7[1] = 1;
    for (ll i = 2; i < N; ++i)
        key9[i] = (key9[i - 1] * 10) % mod, key7[i] = (key7[i - 1] * 10) % 7;

    for (ll i = 0; i < 10; ++i)
        if (i != 7)
        {
            f[1][i][i % 7][i % 7].s0 += 1;
            f[1][i][i % 7][i % 7].s1 += i;
            f[1][i][i % 7][i % 7].s2 += i * i;
        }
    for (int i = 2; i < N; ++i)// 处理到第i位,第i位位j,各位和%7 为 l 的数,数本身%7 为 m 的数
        for (ll j = 0; j < 10; ++j)
        {
            if (j == 7)//
                continue;
            for (int l = 0; l < 7; ++l)
                for (int m = 0; m < 7; ++m)
                    for (int k = 0; k < 10; ++k)
                    {
                        if (k == 7)
                            continue;
                        auto &it1 = f[i][j][ ( l + j ) % 7][( j * key7[i] + m) % 7];//不然数组太长了
                        auto &it2 = f[i-1][k][l][m];
                        it1.s0 = ( it1.s0 + it2.s0) % mod;
                        it1.s1 = ( it1.s1 + it2.s0 * (j * key9[i] % mod ) % mod + it2.s1) % mod;
                        it1.s2 = ( it1.s2 + j * key9[i] % mod * j % mod * key9[i] % mod * it2.s0 % mod +
                                    key9[i] * j % mod * 2 * it2.s1 % mod + it2.s2) % mod;
                    }
        }
}

ll get(ll i, ll j, ll l, ll m, ll lnum)//用公式求平方和
{
    auto &it = f[i][j][l][m];
    ll x = (key9[i + 1] * lnum) % mod;// * 运算和 % 运算优先级相同
    ll res = ( x * x % mod * it.s0 % mod + x * it.s1 % mod * 2 % mod + it.s2) % mod;
   
    return res;
}

ll solve(ll n)
{
    ll k = 0;
    while (n)
        b[++k] = n % 10, n /= 10;
    ll res = 0, i = k;
    ll lsum = 0, lnum = 0;

    for (; i; --i){
        for (ll j = 0; j < b[i]; ++j){
            for (ll l = 0; l < 7; ++l)
                for (ll m = 0; m < 7; ++m)
                {
                    if (j == 7)
                        continue;
                    else if ( ( l + lsum ) % 7 && ( lnum % 7 * key7[i + 1] + m ) % 7 )//特判
                        res = (res + get(i, j, l, m, lnum % mod)) % mod;
                }
        }
        lsum += b[i];
        lnum = lnum * 10 + b[i];
        if (b[i] == 7)
            break;
    }

    
    if (!i && lnum % 7 && lsum % 7)//注意特判这个数本身
        res = ( res + lnum % mod * ( lnum % mod ) ) % mod;
    return res;
}

int main()
{
    
    init();
    ll l, r, T;
    scanf("%lld", &T);
    while (T--)
    {
        scanf("%lld %lld", &l, &r);
        printf("%lld\n", ( ( solve(r) - solve(l - 1) ) % mod + mod ) % mod);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值