转自自己的关于落谷计数器【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 很喜欢数学,但是到 ...
随机推荐
- 20191010-5 alpha week 1/2 Scrum立会报告+燃尽图 03
此作业要求参见[https://edu.cnblogs.com/campus/nenu/2019fall/homework/8748] 一.小组情况 队名:扛把子 组长:迟俊文 组员:宋晓丽 梁梦瑶 ...
- 深入浅出Spring(二)
IoC概念 控制反转(Inversion of Control)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题. 它还有一个名字叫做依赖注入(Dependency Injection).Io ...
- 【Android - 组件】之IntentFilter的匹配规则
我们知道,Activity的启动模式分为两种,分别是显式启动和隐式启动.显式启动需要明确的指定被启动的对象的组件信息,包括包名和类名:而隐式启动需要 Intent 能够匹配目标组件的 IntentFi ...
- java数据类型(大小等),变量定义,各进制书写方法
1. java中字符占两个字节,因为char类型占两个字节(16位),而C,C++中占1字节(8位). 2. 变量定义 第一步:声明(Declaration) 第二步:赋值(Assignment) 这 ...
- Linux基本架构
Linux linux设计思想 1.程序应该小而专一,程序应该尽量的小,且只专注于一件事上,不要开发那些看起来有用但是90%的情况都用不到的特性: 2.程序不只要考虑性能, 程序的可移植性更重要,sh ...
- 【BZOJ2190】【Luogu P2158】 [SDOI2008]仪仗队
前言: 更不好的阅读 这篇题解真的写了很久,改了又改才成为这样的,我不会写题解但我正在努力去学,求通过,求赞... 题目: BZOJ Luogu 思路: 像我这样的数论菜鸡就不能一秒切这题,怎么办呢? ...
- 静态链表-C语言实现
1.静态链表是在没有指针的编程语言里对链表的一种实现2.主要是用数组模拟指针3.在这里,使用结构体使数组的每一个空间可以存储一个数据元素(date)和一个游标(cur),游标的作用相当于链表的指针域, ...
- 2019-2020-11 20199304 《Linux内核原理与分析》 第十一周作业
缓冲区溢出漏洞实验 一.简介 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回 ...
- php权重分配
假设有3个人 能力的权重 分别为 A=>1,B=>2,C=>3,那么当有6个案子的时候 A分配到1个,B分配到2个,C分配到3个,这很合理,但是当案子只有5个,或者有7个的时候, ...
- 利用 FC + OSS 快速搭建 Serverless 实时按需图像处理服务
作者:泽尘 简介 随着具有不同屏幕尺寸和分辨率设备的爆炸式增长,开发人员经常需要提供各种尺寸的图像,从而确保良好的用户体验.目前比较常见的做法是预先为一份图像存放多份具有不同尺寸的副本,在前端根据用户 ...