数位dp-恨7不成妻

这道题是一道数位dp的难题,要想看懂这篇博客的话会有一些困难,所以有如下的要求:能打出数位dp模板和具有初三及以上的数学水平。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4507http://1xuan.top/problem.php?id=1434

题目大意:给定一个区间l~r,求这里面所有既不含7,本身不能整除7,各位数字之后也不能整除7的数的平方和。

大致思路:在没看到“平方和”之前,这道题都非常简单,只是要记录的东西稍多一点而已,本质上还是一种“裸的”数位dp,但是一旦加

“求平方和”,四个字,变数就很多了。因为没法用常规方式记录状态了,因为前面几位取得数不一样会导致加上的数不一样,所以没法

接记录状态,所以只能用数组记录不涉及前几位的状态的方案数(或平方和),所以我们还要推一步怎样求出平方和,这可不是一件简单事。

推导步骤:

说明:在我的过程中x表示后面几位的取得数字的方案,x1表示第一种方案的数值,x2表示第二种方案的数值,以此类推。“^”表示乘方。

推导:ans=(x1+i*10^p)^2+(x2+i*10^p)^2+(x2+i*10^p)^2+...+(xn+i*10^p)^2(平方和)
=(x1^2+2*x1*i*10^p+i*i*10^2p)+(x2^2+2*x2*i*10^p+i*i*10^2p)+...+(xn^2+2*xn*i*10^p+i*i*10^2p)
=(x1^2+x2^2+...+xn^2)+2*(x1+x2+...xn)*i*10^p+(n*i*i*10^2p)

所以得出结论新的平方和的计算需要旧的平方和、本身的和以及总数。所以每一个dp数组用结构体存三个量,分别是num表示方案数,也就是n,

sum表示本身的和,也就是x1+x2+...+xn,而sum_2表示平方和,也就是x1^2+x2^2+...+xn^2。但是别忘了,我们还有一个东西没求,那就是

sum的值还没讲怎么求。所以我接下来继续推导一下sum的值如何计算。

推导:sum=(x1+i*10^p)+(x2+i*10^p)+...+(xn+i*10^p)

=(x1+x1+...xn)+(n*i*10^p)

而方案数n计算直接加等于就行就不予详细说明了。

所以新的sum的求值只需要旧的总和以及方案数,所以我们就能写出代码了。

 1 /*
 2  (x1+i*10^p)+(x2+i*10^p)+...+(xn+i*10^p)
 3 =(x1+x1+...xn)+(n*i*10^p)
 4 
 5  (x1+i*10^p)^2+(x2+i*10^p)^2+(x2+i*10^p)^2+...+(xn+i*10^p)^2
 6 =(x1^2+2*x1*i*10^p+i*i*10^2p)+(x2^2+2*x2*i*10^p+i*i*10^2p)+...+(xn^2+2*xn*i*10^p+i*i*10^2p)
 7 =(x1^2+x2^2+...+xn^2)+2*(x1+x2+...xn)*i*10^p+(n*i*i*10^2p)
 8 */
 9 #include<bits/stdc++.h>
10 #define ll long long
11 using namespace std;
12 const int NR=20;
13 const int mod=1e9+7;
14 int a[NR];// 每位数字的虽大限度,和别的代码的dig一样 
15 ll power[100];// 预处理每个10的次方数 
16 struct Nd
17 {
18     ll num,sum,sum_2;//结构体方便储存 
19 }dp[NR][10][10];
20 /*
21 dp[pos][md1][md2].num表示目前这个数除以7余md1,各位数字之和除以7余md2时,第pos位以后合法的方案数
22 dp[pos][md1][md2].sum表示目前这个数除以7余md1,各位数字之和除以7余md2时,第pos位以后合法所有的方案的形成的数的总和
23 dp[pos][md1][md2].sum表示目前这个数除以7余md1,各位数字之和除以7余md2时,第pos位以后合法所有的方案的形成的数的平方和
24 */
25 Nd tmp;
26 /*
27 在dfs中,pos表示枚举到了第几位
28 md1表示目前这个数除以7的余数
29 md2表示目前这个数的各位数字除以7的余数 
30 */
31 Nd dfs(int pos,int md1,int md2,int limit)
32 {
33     if(!pos)//当这个数的低位都被枚举完了时 
34     {
35         //看这个数是否与7有关,也就是是否合法 
36         tmp.num=(md1!=0)*(md2!=0);
37         tmp.sum=tmp.sum_2=0;
38         return tmp;
39     }
40     if(!limit&&dp[pos][md1][md2].num!=-1) return dp[pos][md1][md2];//如果搜过,直接记忆化 
41     int len=limit?a[pos]:9;//算出最大位,数位dp常规操作不予解释 
42     Nd ans;ans.num=ans.sum=ans.sum_2=0;//定义ans并清空 
43     for(int i=len;i>=0;i--)//枚举这一位可能取那个数 
44     {
45         if(i==7) continue;//当包含7肯定不合法,直接跳过 
46         tmp=dfs(pos-1,(md1*10+i)%7,(md2+i)%7,limit&&(i==len));//用一个tmp暂时存下所谓“旧的”的值 
47         ans.num+=tmp.num;ans.num%=mod;//算出“新的”的方案数,如公式 
48         ans.sum+=tmp.sum+(i*power[pos]%mod)*tmp.num;ans.sum%=mod;//算出“新的”的总和,如公式
49         ans.sum_2+=(i*i*power[pos*2-1]%mod*tmp.num)%mod+(2*i*power[pos]%mod*tmp.sum)%mod+tmp.sum_2;
50         ans.sum_2%=mod;//算出“新的”的平方和,如公式
51     }
52     if(!limit) dp[pos][md1][md2]=ans;//记录入状态 
53     return ans;
54 }
55 ll cal(ll x)
56 {
57     memset(dp,-1,sizeof(dp));//记搜之前清空dp数组 
58     int len=0;
59     while(x)
60     {
61         a[++len]=x%10;
62         x/=10;
63     }//算出每一位的最大限度 
64     return dfs(len,0,0,1).sum_2%mod;//返回答案 
65 }
66 ll read()
67 {
68     ll x=0,f=1;char ch=getchar();
69     while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
70     while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
71     return x*f;
72 }
73 int main()
74 {
75 //    freopen("1.in","r",stdin);
76 //    freopen("1.out","w",stdout);
77     power[1]=1;
78     for(int i=2;i<=50;i++)
79     {
80         power[i]=power[i-1]*10;
81         power[i]%=mod;
82     }//初始化10的次方数 
83     int T=read();//数据总数 
84     while(T--)
85     { 
86         ll l=read(),r=read();//用long long存左右区间 
87         printf("%lld\n",(cal(r)-cal(l-1)+mod)%mod);
88         //计算这个区间的数的平方和,cal(右边界)-cal(左边界-1)就是这个区间的平方和了 
89     } 
90     return 0;
91 }

猜你喜欢

转载自www.cnblogs.com/chen-1/p/12592375.html