hiho #1033 : 交错和

来源:互联网 发布:国产密码算法及应用 编辑:程序博客网 时间:2024/05/21 11:23

题目描述:

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

给定一个数 x,设它十进制展从高位到低位上的数位依次是 a0, a1, ..., an - 1,定义交错和函数:

f(x) = a0 - a1 + a2 - ... + ( - 1)n - 1an - 1

例如:

f(3214567) = 3 - 2 + 1 - 4 + 5 - 6 + 7 = 4

给定 

1405402477702.png

输入

输入数据仅一行包含三个整数,l, r, k(0 ≤ l ≤ r ≤ 1018, |k| ≤ 100)。

输出

输出一行一个整数表示结果,考虑到答案可能很大,输出结果模 109 + 7

提示

对于样例 ,满足条件的数有 110 和 121,所以结果是 231 = 110 + 121。

更多样例:

Input4344 3214567 3
Output611668829
Input404491953 1587197241 1
Output323937411
Input60296763086567224 193422344885593844 10
Output608746132Input100 121 -1
Output120



样例输入
100 121 0
样例输出
231

解题思路:

这道题确实花了我好几天的时间来做。。首先,根据题目的意思,我尝试列举几个样例来得到一般规律,但处处因为各种原因(比如说当在某一位发生进位时。。),导致分析过程异常复杂,而且情况也多种多样,最后不得已,只好放弃找规律这条路。理所当然地,一般规律找不到,就只有暴力来解决,下面是代码:


#include <cstdio>#include <strstream>#include <iostream>using namespace std;int MOD = 1e9+7;/*int cross_sum(long long t){    int res = 0;    strstream ss;    ss << t;    string num;    ss >> num;    int flag = 1;    for(int i = 0; i < num.size(); i++){        res += flag * (num[i] - '0');        flag = -flag;    }    return res;}*/int cross_sum(long long t){    int res = 0, flag = 1;    while(t > 0){        res += flag * (t % 10);        t = t/10;        flag = -flag;    }    if(flag > 0)        res = -res;    return res;}int main(){    //freopen("t.txt", "r", stdin);    long long left, right;    int k;    scanf("%lld%lld%d", &left, &right, &k);    long long res = 0, base = left;    for(; base <= right; base++){        if(cross_sum(base) == k){            res = (res + base) % MOD;        }    }    printf("%lld\n", res);    return 0;}


当然了,这个代码是超时的,当它在我后面的调试中起到很重要的作用!(输出结果,用excel来处理,看看与自己代码输出是否一致)

现在来仔细分析这个代码的复杂度,可以粗略地认为它的时间复杂度为O(n),n为输入的值,可以看到n可以达到10^20的数量级,这确实是很难被接受的,不得已,在网上搜了下,发现这道题是用数位dp来做的,由于之前没怎么接触过,所以就找了道题做了下,做完之后,再来看这道题,就基本有思路了,所以就尝试开始做,这里我采用的是非递归方法,所以会比递归的dfs来做看起来会复杂点,这在之前已经讨论过了,基本思路如下:

dpSum[i][j]  表示长度为i,交错和为j的数的和(注意,这里的数可以以0开头,否则就会少算一些值,例如当确定了最高位是1后,其余i-1位可以以0开头)

dpCnt[i][j]  表示长度为i, 交错和是j的数的个数

由于交错和可以是负数,所以为其加上100,保证其为正数,状态转移方程为:(在程序中要注意溢出问题,这里为了便于理解,就省去了取模操作)

dpSum[i][j] += dpSum[i-1][200-(j-k)] + k * sz[i] * dpCnt[i-1][200-(j-k)] 

其中k取0到9,表示第i位可能的取值,200-(j-k)表示当前的交错和减去第i位的值再取反,sz[n]表示位数为n的最小的数(这里将其作为基底),即当n为2时,sz[n]为10,

dpCnt[i][j] += dpCnt[i][200-(j-k)];

初始状态是:dpCnt[0][0] = 1(这一点很重要),然后将长度为1的值都初始化就可以了。

求完dp数组后,就可以求具体的值了,这里用了个cal函数long long cal(long long n, int target) 表示求0到n-1的交错和为k的数的和:

首先先求出位数少于n的位数的结果,

然后求位数等于n的位数的数的和:

res += dpSum[i-1][200-(target-k)] + (n-n%sz[i+1]+k*sz[i])  * dpCnt[i-1][200-(target-k)] ;

其中 (n-n%sz[i+1]+k*sz[i]) 求出的是该数的比i高的位,和以k作为第i位的数,例如1234567,i = 5,k = 2则这里表示1220000。

整体代码如下,

#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int N = 22, MAX = 209, MOD = 1e9+7;long long dpSum[N][MAX], dpCnt[N][MAX], sz[N] = {0,1};void init(){    memset(dpSum, 0, sizeof(dpSum));    memset(dpCnt, 0, sizeof(dpCnt));    for(int i = 2; i < N; i++){        sz[i] = sz[i-1] * 10;    }    dpCnt[0][100] = 1;    for(int i = 0; i <= 9; i++){        dpSum[1][100+i] = i;        dpCnt[1][100+i] = 1;    }    for(int i = 2; i < N; i++){        for(int j = 0; j <= 200; j++){            for(int k = 0; k <= 9; k++){                dpSum[i][j] += (dpSum[i-1][200-(j - k)]                                + k * sz[i] % MOD * dpCnt[i-1][200-(j - k)])%MOD;                dpSum[i][j] %= MOD;                dpCnt[i][j] += dpCnt[i-1][200-(j - k)];                dpCnt[i][j] %= MOD;            }        }    }}long long cal(long long n, int target){    if(n == -1)        return 0;    int numArray[N], len = 0;    long long tmp = n;    while(tmp){        numArray[++len] = tmp % 10;        tmp /= 10;    }    long long res = 0;    //先求出满足条件的位数小于n的数字的和    for(int i = 1; i < len; i++){        for(int k = 1; k <= 9; k++){            res += ( dpSum[i-1][200-(target-k)]                    + k * sz[i] % MOD * dpCnt[i-1][200-(target-k)] % MOD ) % MOD;            res %= MOD;        }    }    //求位数等于n的位数的满足条件的数字和    for(int i = len; i > 0; i--){        int k = (i == len);        for(; k < numArray[i];k++){            res += (dpSum[i-1][200-(target-k)]                    + (n-n%sz[i+1]+k*sz[i]) % MOD * dpCnt[i-1][200-(target-k)] % MOD ) % MOD;            res %= MOD;        }        target = 200 - (target - numArray[i]);    }    return res;}int main(){    //freopen("t.txt", "r", stdin);    init();    long long left, right;    int k;    scanf("%lld%lld%d", &left, &right, &k);    long long res = cal(right+1, k+100) - cal(left, k+100);    if(res < 0)        res += MOD;    res %= MOD;    printf("%lld\n", res);    return 0;}


当然,这道题也可以用递归来做:

其中,主要注意dp[i][j]保存的是不考虑上下界,且可以以0开头的i位数,其交错和是j的数的和与个数;

node dfs(int pos, int target, int hasCeiling, int len)
返回的是在长度为len,从0到pos位,交错和为target的数的和与个数


#include <cstdio>#include <cstring>#include <iostream>using namespace std;const int N = 22, MAX = 209, MOD = 1e9+7;long long base[N] = {0,1};int numArray[N];struct node{    long long sum, cnt;    node():sum(-1), cnt(-1){}};node dp[N][MAX];node dfs(int pos, int target, int hasCeiling, int len){    node res;    res.sum = res.cnt = 0;    if(pos == 0){        if(target == 100)            res.cnt = 1;        return res;    }    //注意dp中保存的是没有上下限限制,且开头可以为0的数    if(!hasCeiling && len != pos && dp[pos][target].cnt != -1)        return dp[pos][target];    int head = (len == pos);    int tail = hasCeiling ? numArray[pos] : 9;    for(int i = head; i <= tail; i++){        node tmp = dfs(pos-1, 200-(target-i), hasCeiling && i==numArray[pos], len);        if(tmp.cnt > 0){            res.cnt += tmp.cnt;            res.cnt %= MOD;            res.sum += tmp.sum;            res.sum %= MOD;            res.sum += (tmp.cnt * i * base[pos]) % MOD;            res.sum %= MOD;        }    }    if(!hasCeiling && len != pos){        dp[pos][target] = res;    }    return res;}long long cal(long long n, int k){    if(n == -1)        return 0;    int len = 0;    while(n){        numArray[++len] = n % 10;        n /= 10;    }    long long ans = 0;    node tmp;    for(int i = 1; i <= len; i++){        tmp = dfs(i, k, i == len, i);        ans += tmp.sum;        ans %= MOD;    }    return ans;}int main(){    //freopen("t.txt", "r", stdin);    for(int i = 2; i < N; i++)        base[i] = base[i-1] * 10 % MOD;    long long left, right;    int k;    scanf("%lld%lld%d", &left, &right, &k);    /*    cout << cal(right, k+100) << endl;    cout << cal(left-1, k+100) << endl;    */    printf("%lld\n", (cal(right, k+100) - cal(left-1, k+100) + MOD) % MOD );    return 0;}


1 0