转自自己的关于落谷计数器【p1239】的题解
本蒟蒻写这道题用了两天半里大概五六个小时。(我太弱了)
然后这篇题解将写写我经历的沟沟坎坎,详细的分析一下,
但是由于它很长,因此一定还有多余的地方,比如说我的
预处理,可能比较多余。但是我觉得,信息学需要耐心!
不管是写这道题还是写这个题解,我都花了很长时间。
我认为写一道题,最好是自己完全写出来,所以我才自己琢磨了很久,虽然仍然很多不美满,但我可以骄傲的说这是我自己的成果(蒟蒻蜜汁自满)。 所以如果想凭自己做出来,不应该害怕时间的问题(当然比赛是需要效率的)。如果想从题解吸取经验,也应该看得仔细才有用。
因为我不会什么数位dp,看到算法标签里只有递推,于是我来(自信的)挑战了,但我这次终于是自己研究了一道黄题,还是小有成就感的(蒟蒻蜜汁成就感)。
然后我看题了,在我的印象里,我研究过譬如100~1000内有多少3这样的问题,所以我感觉这题应该不差多少,于是我一开始按照每个数量级的区间有多少个数字1~9来写,于是自然的错了。
然后我想到这应该从1开始记录到某一个数量级(个,十,百,千对应1,2,3,4级),数字0~9有多少个,我一开始只是隐约觉得0和1~9是不一样的,所以我就想先算出来这个再想后面怎么做吧。
首先既然是递推的话,肯定要有边界,因此数量级为1的时候,很显然1~9肯定是只出现了一次。于是我开了一个二维数组,f[i][j]其中i表示数字i,j表示数量级。
然后我用一下代码来给边界赋值。
f[0][0]=0;
f[0][1]=1;
for(int i=1;i<=9;i++)
{
f[i][1]=1;
f[i][0]=0;
}//十以内每个数的数量
所以对于后面的每一个数量级,比如1~99,如果把1~9的十位看作0的话,那么可以看到从0~9作十位,每一个数一定会在作为个位出现十次,可以认为是十位的数字控制了个位上数字出现的次数。而如果十位上是某个数的话,比如1作十位,那么10~19,1不仅会作为个位出现1次,也会作为十位出现一次,因此要加上10。这样各个数字会在1~99中出现10+10=20次,那么如果是1~999,在十位上的每个数字会出现多少次? 那就是f[i][2]*10+100;也就是说
f[i][j]=f[i][j-1]*10+10^(j-1)。 为了将这个10^(j-1)方便的运算,我用o[10]来储存, 将o[1]=1;o[2]=10;
这样,f[i][j]=f[i][j-1]*10+o[j];
如此,对于每一个数量级内1~9出现了多少次,就可以用如下代码运算。
o[1]=1;
for(int i=2;i<=10;i++)
{
o[i]=o[i-1]*10;
}//o[i]用来表示在数量级i中,出现了某一个n,要多叠加o[i]个,比如在10^2的数量级中,即1~99中,对于每一个数,都有它作十位的时候,那么除了每十个数的个位它会出现一次,它会作为十位多出现10次,为了叠加方便,o[2]=10,就可以直接叠加上去了。
for(int i=1;i<10;i++)
{
for(int j=1;j<=9;j++)
{
f[j][i]=f[j][i-1]*10+o[i];
}
}//计算1~9的每个数在某一个数量级中的个数
接下来,考虑一下0; 0特殊在一个地方,那就是每一次最高位是不会出现0的。 因为我做到这里的时候比较懒,用了一个打表找规律。
#include<bits/stdc++.h>
using namespace std;
int a,b[10]={};
int main()//打表器
{
int n;
cin>>n;
int h[10]={};
for(int i=1;i<=n;i++)
{
int m=i;
while(m>0)
{
int v;
v=m%10;
for(int j=0;j<=9;j++)
{
if(v==j) h[j]++;
}
m=m/10;
}
}
for(int i=1;i<=10;i++)
{
cout<<h[0]<<endl;
}
return 0;
}
发现了0的递推式
f[0][i]=f[0][i-1]+(i-1) 9 o[i-1];
到了真正使用的时候我才又更深入的思考。 所以说如果这道题只是问某一数量级的0出现次数,其实打表找规律就好。
那么到这时候,我相当于进行了一个预处理。
下一步就是具体的分析了。
对于一个数12345,找到1~12345中各数字出现多少次。 我首先想的是吧10000以前的直接用刚刚的f[i][4]添加给ans[i],然后处理后面的2345。但是这里的调用很麻烦,于是我想到了倒着来处理,从5开始,看看5对答案的贡献,发现它仅仅贡献了0~5这几个数字,每个多一次。那么4呢,贡献给了所有数字f[i][1]*4个结果,(1~40中每个数字先在个位上有一个),对于1~3,它们还作10位,各多出现10次。 所以我想到了,对于1~9的一个处理方式。
while(u>0)
{
u=u/10;
c++;
}
int l=0,z;//z用来表示当前位数上的数字是几
int r[12]={};//r用来补齐某一位上的数出现的次数要加上r。
while(k>0)//从最后一位开始数,出现了多少次某一个数字
{
l++;//表示这是倒数第几位,也相当于多少的数量级,比如l=1时,表示这是个位
z=k%10;//用z提取数字的最后一位
for(int i=1;i<=9;i++)//先判1~9的数
{
ans[i]=ans[i]+f[i][l-1]*z;
if(i<z)
{
ans[i]=ans[i]+o[l]; //如果说在这一位上的数大于i,说明有o[l]个i要作为l位来记录,比如235,210~219中,1会作为十位出现十次,那么就多记录上o[l]个1;
}
if(i==z)
{
ans[i]=ans[i]+1+r[l];//比如235,在记录3时,不仅要记录200~229,230的3也算一次,后面的5算5次,总共是1+r次
}
}
k=k/10;
r[l+1]=r[l]+z*o[l];//这里可以看到 比如 235,对于3 来说 200~230中可以直接用上面算出来,但是后面的231~235,需要加上5,这里5就用r来记录.
}
我的想法都体现在了注释里面。为了不让自己迷糊,我就边写边注释,我觉得这不失是一种在做比较复杂的题(对我这种蒟蒻来说)的时候的一种好方法。
最后这个0,令我“深恶痛绝”(还是我太弱了) 我一开始以为0也可以这样推,但是0太特殊,它在第一位肯定不会有,而在最后一位上又会受前面所有的数控制。
等到真正思考0的答案时,我才想到了“控制出现”的思路。
比如说110,个位上的0出现的次数,受什么控制?百位上的1,控制了0一定会在10~90的个位一共出现9次,十位上的1,则只控制0在100的个位上会出现一次。那么个位上的0总共出现了9+1 =10 次。
十位上的0呢?
在100~109上出现了十次。 于是我就认为,个位上的0受到前面所有数的控制,而十位上的0受到了自己的控制,因为十位上是1,所以个位十位是0的情况一定出现了,这里和1~9的思想一样 其实还是如果这一位上的数字大于0,0作为这一位的情况已经出现了,那么这时候可以认为这一位上的0受前面更高位的控制。
经过几个数的分析,我做出了一下总结。 对于个位上的0,在数量级十位以上,那么它受控制,十位会控制它有几个,百位控制有几十个,例如320,3控制了它有10~99,100~199,200~299的个位上分别有10个零,310-1,因为单个零不计,所以减一就好。2则控制了他从300~320上有3个0。个位本身不控制自己。对于十位来说,百位控制了它的有几十个数,也就是说3,控制了它有100~109,200~209,十位上均有十个0,因为在0~99内十位上没有0,所以就是310-10,这时候,它本身就会控制自己了,因为从300~309有多少个十位上的零,取决于十位上的数,当它>=1时,肯定有300~309的十个,如果为零,则受后边数的控制 ,比如说308,那么十位上出现的次数就加上(8+1),即九次。对于最高位三,他肯定不会出现0;
所以说,对于一个四位数abcd来说,a上没有0,d上的零有abc-1个,c上的0有ab0个(c>=1)或者ab0-10+d个(c=0),对于b来说,b上的0有a00个(b>=1)或者cd个.
所以我就做了开了两个数组,一个t[i]表示i位上的0有多少个,一个r[i]表示i位以后的数字是多少。
int t[10]={},s;
s=n;
for(int i=c;i>=1;i--)
{
if(i==c)
{
t[i]=0;
}
if(i<c&&i!=1)
{
if(s/o[i]>=1)
{
t[i]=(n/o[i+1])*o[i];
}
if(s/o[i]==0)
{
t[i]=(n/o[i+1])*o[i]-o[i]+r[i]+1;
}
}
if(i==1)
{
t[i]=n/o[i+1];
}
s=s%o[i];
}//读取每一位上的数字,并且判断其贡献
for(int i=1;i<=c;i++)
{
ans[0]=ans[0]+t[i];
}
到了这里,整个题基本结束了。中间不管是思路还是细节的处理,(因为我很弱)都花了不少时间,但我觉得这是值得的,是一次锻炼(因为我很弱)。
下面是我AC代码。
#include<bits/stdc++.h>
using namespace std;
int f[10][10];//表示0~9 十个数在每个数量级里的数量
int ans[10]={};
int main()
{
int o[11]={};
o[1]=1;
for(int i=2;i<=10;i++)
{
o[i]=o[i-1]*10;
}
f[0][0]=0;
f[0][1]=1;
for(int i=1;i<=9;i++)
{
f[i][1]=1;
f[i][0]=0;
}//十以内每个数的数量
f[0][2]=9;
for(int i=1;i<10;i++)
{
for(int j=1;j<=9;j++)
{
f[j][i]=f[j][i-1]*10+o[i];
}
}//计算1~9的每个数在某一个数量级中的个数
f[0][2]=9;
for(int i=3;i<10;i++)
{
f[0][i]=f[0][i-1]+(i-1)*9*o[i-1];
} //计算0在每个数量级里的数量
int n;
int k;
cin>>n;
k=n;
int u,c=0;
u=n;
while(u>0)
{
u=u/10;
c++;
}
int l=0,z;
int r[12]={};//r用来补齐某一位上的数出现的次数要加上r。
while(k>0)//从最后一位开始数,出现了多少次某一个数字
{
l++;//表示这是倒数第几位,也相当于多少的数量级,比如l=1时,表示这是个位
z=k%10;//用z提取数字的最后一位
for(int i=1;i<=9;i++)//先判1~9的数
{
ans[i]=ans[i]+f[i][l-1]*z;
if(i<z)
{
ans[i]=ans[i]+o[l];
}
if(i==z)
{
ans[i]=ans[i]+1+r[l];
}
}
k=k/10;
r[l+1]=r[l]+z*o[l];
}
int t[10]={},s;
s=n;
for(int i=c;i>=1;i--)
{
if(i==c)
{
t[i]=0;
}
if(i<c&&i!=1)
{
if(s/o[i]>=1)
{
t[i]=(n/o[i+1])*o[i];
}
if(s/o[i]==0)
{
t[i]=(n/o[i+1])*o[i]-o[i]+r[i]+1;
}
}
if(i==1)
{
t[i]=n/o[i+1];
}
s=s%o[i];
}//读取每一位上的数字
for(int i=1;i<=c;i++)
{
ans[0]=ans[0]+t[i];
}
if(c<=2)//数据小的话就直接枚举
{
int h[10]={};
for(int i=1;i<=n;i++)
{
int m=i;
while(m>0)
{
int v;
v=m%10;
for(int j=0;j<=9;j++)
{
if(v==j) h[j]++;
}
m=m/10;
}
}
for(int i=0;i<=9;i++)
{
cout<<h[i]<<endl;
}
}
if(c>=3)
{
for(int i=0;i<=9;i++)
{
cout<<ans[i]<<endl;
}
}
return 0;
}
码风较乱,算法很菜。 但是有一点,用这个程序去做p2062(紫题),它问的是区间[a,b]里每个数字出现多少次,所以我就用b中每个数字出现的次数减去a-1中每个数出现的次数,A掉了一道紫题。 这样一来,我做一道黄题的时间,其实也相当于花在了一道紫题上了(仍然不能改变蒟蒻的现实)
转自自己的关于落谷计数器【p1239】的题解的更多相关文章
- 洛谷P2832 行路难 分析+题解代码【玄学最短路】
洛谷P2832 行路难 分析+题解代码[玄学最短路] 题目背景: 小X来到了山区,领略山林之乐.在他乐以忘忧之时,他突然发现,开学迫在眉睫 题目描述: 山区有n座山.山之间有m条羊肠小道,每条连接两座 ...
- 【洛谷P3960】列队题解
[洛谷P3960]列队题解 题目链接 题意: Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有 n×m ...
- 洛谷P2312 解方程题解
洛谷P2312 解方程题解 题目描述 已知多项式方程: \[a_0+a_1x+a_2x^2+\cdots+a_nx^n=0\] 求这个方程在 \([1,m]\) 内的整数解(\(n\) 和 \(m\) ...
- 洛谷P1577 切绳子题解
洛谷P1577 切绳子题解 题目描述 有N条绳子,它们的长度分别为Li.如果从它们中切割出K条长度相同的 绳子,这K条绳子每条最长能有多长?答案保留到小数点后2位(直接舍掉2为后的小数). 输入输出格 ...
- 洛谷P2507 [SCOI2008]配对 题解(dp+贪心)
洛谷P2507 [SCOI2008]配对 题解(dp+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/1299251 链接题目地址:洛谷P2507 [S ...
- 洛谷 P1220 关路灯 题解
Description 有 $n$ 盏路灯,每盏路灯有坐标(单位 $m$)和功率(单位 $J$).从第 $c$ 盏路灯开始,可以向左或向右关闭路灯.速度是 $1m/s$.求所有路灯的最少耗电.输入保证 ...
- AC日记——组合数问题 落谷 P2822 noip2016day2T1
题目描述 组合数表示的是从n个物品中选出m个物品的方案数.举个例子,从(1,2,3) 三个物品中选择两个物品可以有(1,2),(1,3),(2,3)这三种选择方法.根据组合数的定 义,我们可以给出计算 ...
- 落谷p3376 最大流EdmondsKarp增广路模板
参考: https://blog.csdn.net/txl199106/article/details/64441994 分析: 该算法是用bfs求出是否有路从s到t, 然后建立反向边(关于反向边), ...
- 落谷P3941 入阵曲
题目背景 pdf题面和大样例链接:http://pan.baidu.com/s/1cawM7c 密码:xgxv 丹青千秋酿,一醉解愁肠. 无悔少年枉,只愿壮志狂. 题目描述 小 F 很喜欢数学,但是到 ...
随机推荐
- HTTP 协议漫谈
转载出处:HTTP 协议漫谈 简介 网络上已经有不少介绍 HTTP 的好文章,对HTTP的一些细节介绍的比较好,所以本篇文章不会对 HTTP 的细节进行深究,而是从够高和更结构化的角度将 HTTP 协 ...
- 复习-java集合简记
1.集合概述 ava集合类存放于 java.util 包中,是一个用来存放对象的容器. 集合只能保存对象(实际上也是保存对象的引用变量),Java主要由两个接口派生而出:Collection和Map, ...
- Python使用场景和应用领域
Python特点 1.Python使用C语言开发,但是Python不再有C语言中的指针等复杂的数据类型. 2.Python具有很强的面向对象特性,而且简化了面向对象的实现.它消除了保护类型.抽象类.接 ...
- T1110-计算线段长度
原题链接: https://nanti.jisuanke.com/t/T1010 题目简述: 已知线段的两个端点的坐标A(Xa,Ya),B(Xb,Yb)A(X_a,Y_a),B(X_b,Y_b)A(X ...
- myql数据库,sql横排转竖排以及竖排转横排,oracle的over函数的使用
一.引言 前些日子遇到了一个sql语句的横排转竖排以及竖排转横排的问题,现在该总结一下,具体问题如下: 这里的第二题和第三题和下面所讲述的学生的成绩表是相同的,这里给大家留一下一个念想,大家可以自己做 ...
- 04-kubernetes 资源清单定义入门
目录 资源对象 创建资源的方法 清单帮助命令 创建测试清单 资源的三种创建方式 资源对象 workload:Pod, ReplicaSet, Deployment, StatefulSet, Daem ...
- CentOS 7 Cobbler 自动化安装系统
在上一篇Cobbler 安装中,配置好了Cobbler,下面来配置自动化安装 配置cobbler-DHCP # 修改settings中参数,由cobbler控制dhcp [root@cobbler ~ ...
- VUE+DRF系列
vue基础系列 001 路飞学诚项目简介 002 Vue简介 003 Vue引入 004 文本指令 005 事件指令 006 斗篷指令 007 属性指令 008 表单指令 009 条件指令 010 路 ...
- 阿里云安装mysql
1. ##数据库字符集SHOW VARIABLES LIKE 'character_set_%'; ##数据库校对股则SHOW VARIABLES LIKE 'collation_%'; 2.创建数据 ...
- mysql那些事(3)小数如何存储
创建mysql数据表的时候,经常会遇到存储小数(浮点数)的情况,如:价格,重量,身高等. 目前大的公司流行三种存储方案: 1.将数据扩大10的倍数达到使用整数类型存储目的. 比如价格,我们经常以分为单 ...