【动态规划】[COCI]摘樱桃

这道题目实际上我们首先看看题目描述
摘樱桃(cherry) 拉娜生活在一个美丽的小村庄。在大街的一旁有一排樱桃树,编号从1到N. 在反复研究后,拉娜发现树的编号神奇的决定了这棵树的樱桃数。 对每一棵树,考虑树的编号中连续的几个数段,对每一数段,该数字乘上这个数段的长度的平方再全部相加,就得到这棵树能够结的樱桃数。 比如,树的编号是77744007,数段分别为777,44,00和7。樱桃数就是7*3^2+4*2^2+0*2^2+7*1^2=86个。 摘樱桃的时候到了,村民们同意把编号为A到B(包括A,B)的所有樱桃都摘下来。写一个程序计算,能摘到多少樱桃。
输入样例一:
1 9
输入样例二:
100 111
输入样例三:
7774407 7774407
输出样例一:
45
输出样例二:
68
输出样例三
86
首先我们可以发现令 f(i) 表示从1-i中的可以摘的樱桃,那么答案应该就是 ans=f(B)f(A1) 那么可以发现这是一个数位DP那么令 f(i,j,d) 表示当第i位为j的数字的所有的樱桃的数量和第三位表示的是当前构成 f(i,j,d) 的编码和是否严格小于A或者B的前i位所构成的数字(因为要特殊处理边界)(i从小到大对应的是A的从高位到个位)
然后分析一下很容易发现

f(i,Ai,1)=f(k,Ak,1)+(ik)2×Ai

那么如果 d=0 呢,可以发现有两种情况先说第一种k位之前的已经严格小于了A了那么当前就不用考虑任何东西所以
f(i,j,0)=k=0,z=0k<i,z<10f(k,z,0)+(ik)2×Ai×g(k,z,0)
这里的 g(i,j,0) 表示的是能够构成 f(k,z,0) 的总的方案数量,因为对于每一种后面都可以接上i-k这么一段,那么如果之前从k已经严格等于了那么就要从k+1开始(高位开始)一直到i判断如果有不相等的了,就判断是大还是小,如果是小于j那么显然当前如果i-k变成j就超过了A了,如果大于那么无论后面是否存在小于j这个数字i-k变成j不会超过A那么有
f(i,j,0)+=f(k,Bk,1)+(ik)2×Ai
然后答案就是
Ans=(i=0i<10f(n,i,0))+f(n,An,1)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 16;
LL dp[MAXN+2][10][2], g[MAXN+2][10][2], B[MAXN+10], n;
LL f(int i, int j, int d){
    if(!i) return 0;
    if(dp[i][j][d]) return dp[i][j][d];
    if(d){
        int nowp=i;
        do{nowp--;}while(B[nowp+1] == B[nowp]);
        dp[i][j][d] = f(nowp, B[nowp], d) + 1LL * (i - nowp) * (i - nowp) * j * g[nowp][B[nowp]][1];
    }else{
        bool flag = B[i] <= j;
        for(int k=i-1; k>=0; k--){
            if(B[k+1] == j) flag=flag;
            else flag=j>B[k+1];
            int len = i - k;
            for(int z=0;z<10;z++)
                if(z != j)
                    dp[i][j][d] += f(k, z, 0) + 1LL * len * len * j * g[k][z][0];
            if(flag) continue;
            if(B[k] != j || !k)
                dp[i][j][d] += f(k, B[k], 1) + 1LL * len * len * j * g[k][B[k]][1];
        }
    }
    return dp[i][j][d];
}
LL solve(LL u){
    if(!u) return 0;
    memset(dp, 0, sizeof dp);
    memset(g, 0, sizeof g);
    g[0][0][1] = 1LL;
    char tmp[MAXN+5];
    memset(tmp, 0, sizeof tmp);
    int pos = 0;
    while(u){
        tmp[pos++] = u % 10 + '0';
        u = u / 10;
    }
    n = strlen(tmp);
    for(int i=0;i<n;i++)
        B[i+1] = tmp[n-i-1]-'0';
    for(int i=1;i<=n;i++){
        for(int j=0;j<10;j++){
            for(int k=0;k<10;k++)
                g[i][j][0] += g[i-1][k][0];
            if(j<B[i]) g[i][j][0] += g[i-1][B[i-1]][1];
        }
        g[i][B[i]][1] = 1;
    }
    LL ret = f(n, B[n], 1);
    for(int z=0;z<10;z++)
        ret += f(n, z, 0);
    return ret;
}
int main(){
    LL a, b;
    while(cin>>a>>b){
        cout<<solve(b) - solve(a-1)<<endl;
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值