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

来源:互联网 发布:试用源码 编辑:程序博客网 时间:2024/05/17 18:03

题目链接:点击打开链接
题意:
求区间[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 0