hiho #1033 : 交错和

题目描述:

时间限制: 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。

更多样例:

Input
4344 3214567 3
Output
611668829
Input
404491953 1587197241 1
Output
323937411
Input
60296763086567224 193422344885593844 10
Output
608746132
Input
100 121 -1
Output
120



样例输入
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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值