ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[周末训练]数字计数

2019-09-30 13:52:19  阅读:264  来源: 互联网

标签:20 训练 周末 int len 计数 small prezero dp


题目

【内存限制:$512MiB$】  【时间限制:$1000ms$】 【标准输入输出】  【题目类型:传统】  【评测方式:文本比较】

【题目描述】

原题来自:ZJOI 2010

给定两个正整数$a$和$b$,求在$[a,b]$中的所有整数中,每个数码$(digit)$各出现了多少次。

【输入格式】

仅包含一行两个整数 ,含义如上所述。

【输出格式】

包含一行$10$个整数,分别表示$0~9$在$[a,b]$中出现了多少次。

【样例】

样例输入

1 99

样例输出

9 20 20 20 20 20 20 20 20 20

题解

【做题经历】

做这道题的时候,如果不是老师说这是个$dp$题,我可能还会直接上暴搜虽然最后我的正解代码也是暴搜

首先,我尝试了一下手推数据,发现还是多好想的,但是始终想不出来这个题和$dp$之间有什么联系。

然后我就两眼一抹黑了

【正解】

这是一道数位$dp$题

在某谷上逛了几圈,还是发现有大佬写的$dp$正解(orz膜拜大佬),然而我太笨了,看不懂$dp$的来历(我这个蒟蒻$dp$也就这样了),含泪的时候,发现了搜索的做法。

首先,我们先把问题转化一下,用与做琪露诺数一样的方法,将题目要求的区间$[a,b]$变成区间$[1,b]$与区间$[1,a-1]$中数码出现的次数。

现在我们要解决的就是怎么求区间$[1,k]$中每个数码出现的次数了。

先看我的$dfs$部分吧

int dfs(int len,bool small,int sum,bool prezero,int d){
    if(len==0)return sum;int ret=0;
    for(int i=0;i<10;++i){
        if(small&&i>num[len])break;
        ret+=dfs(len-1,small&&(i==num[len]),sum+((!prezero||i>0)&&(i==d)),prezero&&(i==0),d);
    }
    return ret;
}

这个$dfs$是不是感觉很清晰啊。

咳咳,我来解释一下,这个$dfs$包含五个参数,他们分别是:

  • $len$:枚举到这个数的第几位了
  • $small$:是否需要小于等于右边界的对应位数
  • $sum$:显而易见,已经有多少种情况了
  • $prezero$:当前构造的数是否有前导$0$
  • $d$:当前询问的数码

其中$small$可能有点难以理解,这里我们可以举一个例子

假设我们的右边界$k=1388$

而当前我们所构造的数是$Num=\overline{13ix}$($x$是我们还没有枚举到的位数)

很显然,$i≤8$,不然$Num$就超过了右边界

而这就是$small$的用处

解决了这个问题,接下来就是搜索都会面临的问题——时间复杂度

显而易见,如果是这个代码裸交,是会被$T$飞的

所以我们需要加入记忆化减少大部分重复搜索的操作

这里而这个记忆化其实只需要把所有搜索的参数全部塞进定义和下标中就可以了(因为这个题的范围十分友善)

那么就有记忆化$dp[len][small][sum][prezero]$,其定义就是搜索对应的参数

因为我们是分字码搜索,所以最后一维可以省略了

然后就是你们最喜欢的代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x; 
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
const int MAXSIZE=20;
int l,r,num[MAXSIZE+5];
int dp[MAXSIZE+5][2][MAXSIZE+5][2];
int dfs(int len,bool small,int sum,bool prezero,int d){
    if(len==0)return sum;
    if(dp[len][small][sum][prezero]!=-1)return dp[len][small][sum][prezero];
    int ret=0;
    for(int i=0;i<10;++i){
        if(small&&i>num[len])break;
        ret+=dfs(len-1,small&&(i==num[len]),sum+((!prezero||i>0)&&(i==d)),prezero&&(i==0),d);
    }
    return dp[len][small][sum][prezero]=ret;
}
inline int solve(int var,int d){
    int len=0;
    memset(dp,-1,sizeof dp);
    while(var)num[++len]=var%10,var/=10;
    return dfs(len,1,0,1,d);
}
signed main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    qread(l,r);
    for(int i=0;i<10;++i)printf("%lld ",solve(r,i)-solve(l-1,i));
    putchar('\n');
    return 0;
}

标签:20,训练,周末,int,len,计数,small,prezero,dp
来源: https://www.cnblogs.com/MachineryCountry/p/11612360.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有