zoukankan      html  css  js  c++  java
  • 数位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
    ",(cal(r)-cal(l-1)+mod)%mod);
    88         //计算这个区间的数的平方和,cal(右边界)-cal(左边界-1)就是这个区间的平方和了 
    89     } 
    90     return 0;
    91 }
  • 相关阅读:
    20165101刘天野 2017-2018-2 《Java程序设计》 结对编程练习_四则运算(第一周)
    20165101刘天野 2017-2018-2 《Java程序设计》第6周学习总结
    20165101 实验一 Java开发环境的熟悉
    20165101刘天野 2017-2018-2 《Java程序设计》第5周学习总结
    HTML——meta
    CSS——改变浏览器滚动条样式
    HTML5——移动端的点击、拖拽
    JS高级——弹出框的美化
    JS高级——监听浏览器的返回事件
    JS高级——文件操作
  • 原文地址:https://www.cnblogs.com/chen-1/p/12592375.html
Copyright © 2011-2022 走看看