【上海交大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\n", result);

56 }
View Code

 

 

你可能感兴趣的:(dp)