[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 ...
随机推荐
- restTemplate访问接口
后端技术精选 每天推送精选技术好文,涉及Java.python.Linux及MySQL,欢迎关注微信公众号:后端技术精选 随笔 - 52, 文章 - 0, 评论 - 50, 引用 - 0 Spring ...
- SpringBoot学习:整合MyBatis,使用Druid连接池
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)添加pom依赖: <!-- https://mvnrepository.co ...
- Python3开启Http服务
在CMD命令行输入D: 切换到D盘, 然后输入 python -m http.server 8000 开启HTTP服务: 在浏览器地址栏输入 http://localhost:8000/
- Git一分钟系列--快速安装git客户端
在项目开发过程中,几乎所有公司都会用到版本控制工具来管理自己的项目资源文件,比如Git,SVN. 什么是svn? 版本控制软件,通过svn来实现版本控制首先需要搭建一个服务器,在服务器上创建仓库保存项 ...
- 一篇文章让你了解GC垃圾回收器
简单了解GC垃圾回收器 了解GC之前我们首先要了解GC是要做什么的?顾名思义回收垃圾,什么是垃圾呢? GC回收的垃圾主要指的是回收堆内存中的垃圾对象. 从根对象出发,所有被引用的对象,都是存活对象 其 ...
- [JSON].remove( keyPath )
语法:[JSON].remove( keyPath ) 返回:无 说明:移除指定路径的键 示例: Set jsonObj = toJson("{div:{'#text-1': 'is tex ...
- [C++]STL中的容器
C++11 STL中的容器 一.顺序容器: vector:可变大小数组: deque:双端队列: list:双向链表: forward_list:单向链表: array:固定大小数组: string: ...
- pymsql报错:UnicodeEncodeError: 'latin-1' codec can't encode characters End,OK!!
UnicodeEncodeError: 'latin-1' codec can't encode characters的做法基本一致,后来发现是因为使用的是mysqldb,照着网上的方法修改配置应该可 ...
- c字符指针与字符数组的区别
1.定义 char *pchar; //定义了指针,没赋值之前不能使用.如果:printf("*pchar:%c\n",*pchar); 出现段错误Segmentation fa ...
- 线性代数之——A 的 LU 分解
1. A = LU 之前在消元的过程中,我们看到可以将矩阵 \(A\) 变成一个上三角矩阵 \(U\),\(U\) 的对角线上就是主元.下面我们将这个过程反过来,通一个下三角矩阵 \(L\) 我们可以 ...