[ZJOI2010]数字计数 数位DP
最近在写DP,今天把最近写的都放上来好了,,,
题意:给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
首先询问的是一个区间,显然是要分别求出1 ~ r ,1 ~ l的答案,然后相减得到最终答案
首先我们观察到,产生答案的区间是连续的,且可以被拆分,
也就是说0 ~ 987的贡献= 0 ~ 900 + 901 ~ 987的贡献,
同理,把位拆开也是等价的,所以我们可以单独计算每个位的贡献
这样讲可能有点不太清晰,举个例子吧
3872
我们先把它按数拆开来算:分为0 ~ 999 + 1000 ~ 3872
然后再把1000 ~ 3872的部分按位拆开来算
设f[i][j]为到了第i位j出现的次数(i=2 00 ~ 99),注意允许前导0的存在,只是必须是共i位
对于一个数n,若有t位,那么前面那部分是t-1位的,后面则是计算t位的(因为前面t-1位没有限制)
因此我们先加上t-1位的数字出现次数,
首先当i=1时,显然f[i][j]=1,
当i变为2时,我们可以脑补一下,2位可以拆分为1位+1位,而前面的1位有10种可能,因此后面的1位会出现10次,
而前面的一位也因此使得每种数字要+10,
当i变为3时,我们可以再次脑补,3位可以拆分为1位+2位,前面的一位还是有10种可能,因此后面的2位会出现10次,
而前面的一位也因此使得每种数字要+100
……
于是观察可得
f[i][j] = f[i-1][j] * 10 + 10^(i-1);
因此第一部分:
ans[j] += f[cnt-1][j];
为什么只要加这个呢?
因为这个是从0开始的啊!
比如当cnt-1==3时,
f[cnt-1][j]记录的就是000 ~ 999的出现次数,
显然包括了1位2位3位的,
但是这样会有前导0,怎么办呢?
把数字列出来稍微观察一下即可得知:
对于任意一个数而言,其前导0个数为总位数减去有效位数
也就是说0位的数有3个前导0,如000
1位的数有2个前导0,如002
2位的数有1个前导0,如078
3位的数没有前导0,如364
因此我们只需要分别减去这些位数的前导0即可,
每个位数要减掉的前导0为:对应的前导0个数 * 有多少个数
比如1位的数有2个前导0,那么这部分的前导0个数为:2 * 9
而0位的数的前导0个数为:3 * 1
这样可以观察到在某种情况下后面要乘的那个数是不好计算的,
所以我们通过前缀和来计算它,
第0位时,一共有1个数,
第1位时,一共有10个数(包括0位的),
感觉有点说不清了。。。。
就是说
000 ----> 1个数
001
。
。
009 ---->10个数
010
011
。
。
099 ---->100个数
而我们要做的就是要获取单独的每一段(被换行隔开的)有多少个数
那么很显然
010 ~ 099的数的个数就是100 - 10,
所以我们在计算的时候记录第一个sum表示在当前位之前的前缀和,t表示包括当前位的数的总个数,
比如计算到010 ~ 099这一段的时候,sum = 10,t=100,个数为t - sum = 90个
于是现在我们就可以开始计算第2部分了:
1000 ~ 3872
对于这部分,我们按位处理,
方法不太好说,还是用例子说明吧,
从高位开始算,
最高位是3,
那么很显然1000~2999这部分是满的(即都可以取到),因此我们计算的方法就是
用第一位 + 后3位,
对于1000 ~ 1999来说,第一位的1出现了1000次,我们加上,
那后面的部分对应的次数就是000 ~ 999 的次数对不对!也就是f[3][j]了
2000 ~ 2999同理即可,
因此我们要加的f[3][j]的个数就是2
那么我们现在只需要处理3000~3872了,
同样按位处理,
第一位的3出现了873次(还有000的一次),我们加上,
然后就是要计算872的贡献了,
那这部分怎么求呢?
和计算3872是一样的啊(是不是很像递归),
还是先算000~799
但是也不完全一样,有一点不同,
因为此时已经不再是第一位了,所以前导0是允许存在的,
所以这个时候,在处理第一位的时候就需要考虑
000 ~ 099了,
而在第一位时只需从100 ~ 199开始考虑,
多加上对应的次数即可。
貌似有点啰嗦了。。。。
下面是代码
- #include<bits/stdc++.h>
- using namespace std;
- #define R register int
- #define LL long long
- #define AC 15
- LL l,r,cnt,tot;
- LL ansl[AC],ansr[AC],f[AC][AC];//f[i][j] : 到了第i位(i = 2,00 ~ 99),j出现的次数
- int numl[AC],numr[AC];
- /*由于枚举位数,所以不考虑前导零,
- 因此就可以直接递推次数了?
- 次数必须严格保证是i位的,就是说就算是000也要凑够i位
- */
- void pre()
- {
- scanf("%lld%lld\n",&l,&r);
- l -= ;//因为我计算的是r - l的,但是l要包括进来,,,,懒得改了,就在这里减一下吧
- LL tmp=l;
- while(tmp)
- {
- numl[++cnt]=tmp % ;
- tmp /= ;
- }
- tmp=r;
- while(tmp)
- {
- numr[++tot]=tmp % ;
- tmp /= ;
- }
- for(R i=;i<=;i++) f[][i]=;
- tmp=;
- for(R i=;i<=tot;i++)//枚举位数
- {
- for(R j=;j<=;j++)
- f[i][j] = f[i-][j] * + tmp;//因为新加入的第一位有10种,所以原来的位数要乘10
- tmp *= ;
- }
- /*for(R i=1;i<=tot;i++)
- {
- for(R j=0;j<=9;j++)
- printf("%lld ",f[i][j]);
- printf("\n");
- }*/
- }
- void work1()
- {
- LL tmp=;
- for(R i=;i<cnt;i++) tmp *= ;
- for(R j=;j<=;j++) ansl[j] += f[cnt-][j];//就是这里会产生前导零吧,不过是整段的,可以稍作分析解决
- LL t=,sum=;//现在是t个数码
- for(R i=;i<cnt;i++)//枚举位数(预先加的只有cnt-1位的ans,因为只有这部分是完整的)
- {
- ansl[] -= (t - sum) * (cnt - - i);//i位被占用,剩下的都是0
- sum += t - sum;//累加上当前的数码个数
- t *= ;//10 ^ i个数码
- }
- for(R i=cnt; i ;i--)
- {
- int b = (i == cnt);//这里和下面都要特判第一位(因为有0取与不取的问题,即开头是否可以为0)
- for(R j=b;j<numl[i];j++)//先统计当前位的
- ansl[j] += tmp;
- l -= numl[i] * tmp;
- ansl[numl[i]] += l + ;
- if(i == cnt)//error!!!必须大于等于1才行
- {
- for(R j=;j<=;j++)
- ansl[j] += f[i-][j] * (numl[i] - );//这里只能批量加上前几个的,后面的是不规则的,要到后面处理
- }
- else
- {
- for(R j=;j<=;j++)
- ansl[j] += f[i-][j] * numl[i];//因为之后可以算0的了,所以就不要-1了
- }
- tmp /= ;
- }
- }
- void work2()
- {
- LL tmp=;
- for(R i=;i<tot;i++) tmp *= ;
- for(R j=;j<=;j++) ansr[j] += f[tot-][j];//就是这里会产生前导零吧,不过是整段的,可以稍作分析解决
- LL t=,sum=;//现在是t个数码
- for(R i=;i<tot;i++)//枚举位数(预先加的只有tot-1位的ans,因为只有这部分是完整的)
- {
- ansr[] -= (t - sum) * (tot - - i);//i位被占用,剩下的都是0
- sum += t - sum;//累加上当前的数码个数
- t *= ;//10 ^ i个数码
- }
- for(R i=tot; i ;i--)
- {
- int b = (i == tot);//因为只有第一位的0不合法,所以后面是可以统计到0的
- for(R j=b;j<numr[i];j++)//先统计当前位的
- ansr[j] += tmp;
- r -= numr[i] * tmp;//这里本意就是要获取低于当前位的数码,因此前面减掉了的要在r里面也减掉,不然获取不出来
- //ansr[numr[i]] += r - numr[i] * tmp + 1;
- ansr[numr[i]] += r + ;//因为前面已经减过了,所以这里就不要减了
- if(i == tot)//error!!!必须大于等于1才行
- {
- for(R j=;j<=;j++)
- ansr[j] += f[i-][j] * (numr[i] - );//这里只能批量加上前几个的,后面的是不规则的,要到后面处理
- }
- else
- {
- for(R j=;j<=;j++)
- ansr[j] += f[i-][j] * numr[i];//因为之后可以算0的了,所以就不要-1了
- }
- tmp /= ;
- }
- /*for(int i=0;i<=9;i++) printf("%lld ",ansl[i]);
- cout << endl;
- for(int i=0;i<=9;i++) printf("%lld ",ansr[i]);
- cout << endl;*/
- for(R i=;i<;i++) printf("%lld ",ansr[i] - ansl[i]);
- printf("%lld\n",ansr[] - ansl[]);
- }
- int main()
- {
- // freopen("in.in","r",stdin);
- pre();
- work1();
- work2();
- // fclose(stdin);
- return ;
- }
[ZJOI2010]数字计数 数位DP的更多相关文章
- UVA.1640.The Counting Problem / BZOJ.1833.[ZJOI2010]数字计数(数位DP)
题目链接 \(Description\) 求\([l,r]\)中\(0,1,\cdots,9\)每个数字出现的次数(十进制表示). \(Solution\) 对每位分别DP.注意考虑前导0: 在最后统 ...
- Luogu P2602 [ZJOI2010]数字计数 数位DP
很久以前就...但是一直咕咕咕 思路:数位$DP$ 提交:1次 题解:见代码 #include<cstdio> #include<iostream> #include<c ...
- 洛谷P2602 [ZJOI2010]数字计数(数位dp)
数字计数 题目传送门 解题思路 用\(dp[i][j][k]\)来表示长度为\(i\)且以\(j\)为开头的数里\(k\)出现的次数. 则转移方程式为:\(dp[i][j][k] += \sum_{t ...
- [luogu2602 ZJOI2010] 数字计数 (数位dp)
传送门 Description 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. Input 输入文件中仅包含一行两个整数a.b,含义如上所述. Output ...
- 【题解】P2602 数字计数 - 数位dp
P2602 [ZJOI2010]数字计数 题目描述 给定两个正整数 \(a\) 和 \(b\) ,求在 \([a,b]\) 中的所有整数中,每个数码(digit)各出现了多少次. 输入格式 输入文件中 ...
- bzoj1833: [ZJOI2010]count 数字计数(数位DP+记忆化搜索)
1833: [ZJOI2010]count 数字计数 题目:传送门 题解: 今天是躲不开各种恶心DP了??? %爆靖大佬啊!!! 据说是数位DP裸题...emmm学吧学吧 感觉记忆化搜索特别强: 定义 ...
- 1833: [ZJOI2010]count 数字计数——数位dp
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1833 省选之前来切一道裸的数位dp.. 题意 统计[a,b]中0~9每个数字出现的次数(不算 ...
- [bzoj1833][ZJOI2010]count 数字计数——数位dp
题目: (传送门)[http://www.lydsy.com/JudgeOnline/problem.php?id=1833] 题解: 第一次接触数位dp,真的是恶心. 首先翻阅了很多很多一维dp,因 ...
- bzoj1833: [ZJOI2010]count 数字计数 数位dp
bzoj1833 Description 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. Input 输入文件中仅包含一行两个整数a.b,含义如上所述. O ...
随机推荐
- stm32 nucleo系列开发板的接口
1. 首先说的是 Arduino接口,所有的nucleo系列都支持这个接口,而且像F767这种尺寸长的板子也有的,不过是双排排针,其实就是外侧也是 Arduino接口 2. 板子接上电脑之后有个串口
- AFD运维
1.afd 网址:https://www.dwd.de/AFD/html-en/contents.html 2.问题:拷贝了一个主机A配置后(HOST_CONFIG主机项),修改为另一个主机B配置:然 ...
- Linker加载so失败问题分析
WeTest 导读 近期测试反馈一个问题,在旧版本微视基础上覆盖安装新版本的微视APP,首次打开拍摄页录制视频合成时高概率出现crash. 那么我们直奔主题,看看日志: 另外复现的日志中还出现如下信息 ...
- 错误码:2003 不能连接到 MySQL 服务器在 (10061)
今天在ubuntu上安装了mysql服务器,在windows上用客户端软件连接mysql服务器时,出现错误: 错误码: 不能连接到 MySQL 服务器在 () 折腾来折腾去没搞好,防火墙也关了,330 ...
- MySQL连接本地数据库时报1045错误的解决方法
navicat for MySQL 连接本地数据库出现1045错误 如下图: 说明连接mysql时数据库密码错误,需要修改密码后才可解决问题: 解决步骤如下: .首先打开命令行:开始->运行 ...
- 用Anko和Kotlin实现Android上的对话框和警告提示(KAD 24)
作者:Antonio Leiva 时间:Mar 9, 2017 原文链接:https://antonioleiva.com/dialogs-android-anko-kotlin/ 借助Builder ...
- Linux命令应用大词典-第8章 日期和时间
8.1 cal:显示日历信息 8.2 date:显示和设置系统日期和时间 8.3 hwclock:查看和设置硬件时钟 8.4 clock:查看和设置硬件时钟 8.5 clockdiff:主机之间测量时 ...
- TPO-11 C2 Work for the biology committee
committee 委员会 representative 代表 department secretary 系里的秘书 applicant 申请人 TPO-11 C2 Work for the biol ...
- spark-shell解析
spark-shell 作用: 调用spark-submit脚本,如下参数 --classorg.apache.spark.repl.Main --name "Spark shell&quo ...
- 国内版Office365实现MFA的方案(未完)
现在二十一世纪互联版也可以实现了MFA,现在也就是2017年3月份,支持了PC,但是对移动端应用还是不支持的,请了解. 具体方法如下: 登录国内版Office365(事例为高级商业版 https:// ...