• 【上海交大oj】数学题3(数位dp)


    题目描述

    给定一个数字,他在十进制下从高位到低位一次是n0, n1, n2, n3,...

    那么定义它的“差和”为n0-n1+n2-n3+...

    如:十进制数字abcdefg,每个字母代表一个位,那么差和为a-b+c-d+e-f+g。

    所以十进制数字1234567差和为1-2+3-4+5-6+7=4

    现在给你们一个闭区间[m, n],请求出区间内差和为x的数字个数。

    输入格式

    输入只有一行,三个数字,m n x

    30%: 0<= m <= n <=10^3, 50%: 0<= m <= n <=10^8, 100%: 0<= m <= n <=10^18

    -100<= x <=100

    输出格式

    输出只有一个数字,表示区间内差和为x的数字的总和mod1000000007的结果。

    Sample Input

    100 111 1
    

    Sample Output

    211

    这题不会做,妥妥的。为了看懂助教的代码,费了好大劲儿,干脆加点注释,防止以后有用。
    要点:
    1.由于区间太大,无法遍历,所以要把一个数按位处理,有点转化为数组的意思。
    2.动归记录的数组中有遍历所有和差值,为了能用数组下标表示,把-100-100转化到0-200,计算一个数的和差值时加上100,这样虽然和差值变了,但对应和差值的数是没有变的。
    3.需要同时更新满足和差值的所有数的和以及个数,之所以要记录个数,是为了加上最高位对应的值,比如首位为1长度为5的情况,它的数之和为所有对应满足的长度为4的数之和加上 个数*10000.
    4.在最高位确定情况下,它的子问题(即位数少一位)的首位数是可以为零的。

    具体看代码及注释:
     1 #include <cstdio>
     2 #define LIMIT 1000000007  
     3 
     4 long long dp[22][10][220][2] = {}; //分别表示数字的长度,数字首位数,和差的值+100,满足和差值的所有数的和以及个数 
     5 long long basics[22] = {0, 1};  //预处理的数组,方便使用,分别为0,1,10,100,。。。 
     6 long long lower, upper, sum; //三个输入的值 
     7 
     8 long long GetResult(long long number){
     9     int len = 0;
    10     for(long long i = number; i; i /= 10, ++len);
    11     long long result = 0;
    12     for(int i = 1; i < len; ++i) //位数小于len的情况,首位数不能为零 
    13         for(int j = 1; j < 10; ++j)
    14             result = (result + dp[i][j][sum + 100][0]) % LIMIT;
    15     long long num = 0;
    16     // 下面这个比较复杂,比如3333这个数,首先对于首位小于3长度为4的情况,直接加上dp中的值(此时num为0)
    17     // 但是对于3则不行,因为不包含所有子情况,所以单独处理,也就是数字为333的情况的和加上 个数*3000(此时num为3)
    18     //对于333也是一样,达到3的时候处理33加上(3300 *个数) 
    19     for(int i = len, tmp_sum = sum + 100, j; i > 0;
    20          number -= basics[i] * j, --i, tmp_sum = 200 - (tmp_sum - j), num = num * 10 + j){
    21         for(j = 0; basics[i] * (j+1) <= number; ++j){
    22             if (i == len && j == 0)  //排除长度为len时首位为0,长度小于len首位可以为0 
    23                 continue;
    24             result += dp[i][j][tmp_sum][0] + dp[i][j][tmp_sum][1] * (num * basics[i+1] % LIMIT) % LIMIT;
    25         }
    26         result %= LIMIT;
    27     }
    28     return result;
    29 }
    30 
    31 int main(){
    32     for(int i = 2; i < 22; ++i)
    33         basics[i] = basics[i-1] * 10;
    34     scanf("%lld%lld%lld", &lower, &upper, &sum);
    35     for(int i = 0; i < 10; ++i){ // 长度为一时的处理 
    36         dp[1][i][i+100][0] = i; 
    37         dp[1][i][i+100][1] = 1;
    38     }
    39     for(int i = 2; i < 22; ++i)
    40         for(int j = 0; j < 10; ++j)
    41             for(int k = 0; k < 200; ++k){
    42                 for(int m = 0; m < 10; ++m){
    43                     dp[i][j][k][0] += dp[i-1][m][200 - (k - j)][0]; //首位数为j,和差为k对应的长度为i-1的和差为200-(k-j) 
    44                     dp[i][j][k][1] += dp[i-1][m][200 - (k - j)][1]; //遍历所有子问题并更新 
    45                 }
    46                 dp[i][j][k][0] += dp[i][j][k][1] * (j * basics[i] % LIMIT) % LIMIT; //加上最高位的对应值 
    47                 dp[i][j][k][0] %= LIMIT;
    48                 dp[i][j][k][1] %= LIMIT;
    49             }
    50     long long result_upper = GetResult(upper+1);
    51     long long result_lower = GetResult(lower);
    52     long long result = result_upper - result_lower;
    53     if(result < 0)
    54         result += LIMIT;
    55     printf("%lld
    ", result);
    56 }
    View Code
     
  • 相关阅读:
    python学习笔记之生成器和迭代器、内置函数
    python学习笔记之装饰器(语法糖)
    python学习笔记第三天
    python学习笔记第二天
    Python学习笔记第一天
    获取系统所有软件的卸载信息
    读取注册表获取计算机上已安装程序的信息
    关机重启后运行父进程
    系统关机重启,提升进程权限
    修改用户密码
  • 原文地址:https://www.cnblogs.com/wenma/p/4681585.html
Copyright © 2020-2023  润新知