【上海交大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

 

 
posted @ 2015-07-27 23:34  文_码  阅读(400)  评论(0编辑  收藏  举报