数位dp-恨7不成妻
这道题是一道数位dp的难题,要想看懂这篇博客的话会有一些困难,所以有如下的要求:能打出数位dp模板和具有初三及以上的数学水平。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4507或http://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 }