数位dp:统计1到n中有多少数包含2018的子串(2018可以不连续)

本文深入探讨了一个数据规模达到10^10级别的动态规划算法实现,详细介绍了如何通过递归与备忘录方法解决特定字符串模式计数问题,包括状态定义、转移方程设计及代码实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据范围:10^10

  • 分析:dp[pos][pre] = Node { ll x, ll y, ll z,ll k, ll none}
    • x, y, z, k, none分别代表包含8, 18, 018, 2018子串的数量, 除前边的剩下的数量.
    • pos代表第几位,pre代表pos+1的数
    • 注意:当一个数被加了之后,后边就不能加了(即数只能被加一次)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100 + 10;

int bits[maxn];
struct Node {
    ll x;
    ll y;
    ll z;
    ll k;
    ll none;
};
Node dp[maxn][maxn];

Node dfs(int pos, int pre, bool flag) {
    if(pos < 0) {
        return (Node){pre == 8 ? 1 : 0, 0, 0, 0, pre == 8? 0 : 1};
    }
    if(pre != -1 && !flag && dp[pos][pre].x != -1) {
        return dp[pos][pre];
    }
    int dep = flag ? bits[pos] : 9;
    Node ret = (Node){0, 0, 0, 0};
    for(int i = 0; i <= dep; i ++) {
        Node t = dfs(pos - 1, i, flag && i == dep);
        if(i == 8) {
            ret.x += t.none;
        } else if(i == 1) {
            ret.y += t.x;
        } else if(i == 0) {
            ret.z += t.y;
        } else if(i == 2) {
            ret.k += t.z;
        }
        if(i != 1) {
            ret.x += t.x;
        }
        if(i !=  0) {
            ret.y += t.y;
        }
        if(i != 2) {
            ret.z += t.z;
        }
        ret.k += t.k;
        if(i != 8) {
            ret.none += t.none;
        }
        
    }
    // cout << pos << " " << pre << " " << flag  << ": " << ret.x  << " " << ret.y << " " << ret.z << " " << ret.k  << " " << ret.none << endl;
    if(!flag && pre != -1) {
        dp[pos][pre] = ret;
    }
    return ret;
}
ll solve(ll n) {
    int len = 0;
    while(n) {
        bits[len ++] = n % 10;
        n /= 10;
    }
    return dfs(len - 1, -1, true).k;
}

int main()  {
    for(int i = 0;i < maxn; i ++) {
        for(int j = 0; j < 10;j ++) {
            dp[i][j].x = -1;
        }
    }
    ll n;
    while(cin >> n) {
        cout << solve(n) << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值