数17
Problem Description
小cos是寒树中学一名机敏睿智的男生。最近他偶然地发现同班的小cot——极为神秘的女生——正想方设法地收集他的信息。小cos非常不解。这天他收到了来自小cot的一封邮件,要他数出从a到b(包括a和b)的正整数中共有多少个“17”。(如817517有两个“17”)
小cot打着什么算盘?“17”蕴含着什么不为人知的秘密?……不,你不需要管这些……你的任务是回答小cot的问题。
小cos是寒树中学一名机敏睿智的男生。最近他偶然地发现同班的小cot——极为神秘的女生——正想方设法地收集他的信息。小cos非常不解。这天他收到了来自小cot的一封邮件,要他数出从a到b(包括a和b)的正整数中共有多少个"17"。(如817517有两个"17")
小cot打着什么算盘?"17"蕴含着什么不为人知的秘密?……不,你不需要管这些……你的任务是回答小cot的问题。
Input
第1行两个正整数a和b,意义见问题描述。
Output
输出一个整数,表示a到b的正整数中"17"的数量。
Sample Input
1 200
Sample Output
12
Data Constraint
对于30%的数据,有a≤b≤1,000
对于50%的数据,有a≤b≤100,000
对于100%的数据,有a≤b,a,b均在 int 范围内。
首先,看到题目,我们很容易知道,它询问的是,对于一个区间 [a,b],里面所有的数中,有多少个"17",例如 117 有1个"17", 1717171 有6个"17"。
那么,我们很容易做出一个简单的暴力程序,计算出一个数有多少个"17"。
代码如下。
#include <cstdio>
#define LL long long
using namespace std;
LL a,b,ans;
LL find(LL a)
{
LL p=0;
while (a)
{
if (a%100==17)
p++,a/=100;
else
a/=10;
}
return p;
}
int main()
{
scanf("%lld%lld",&a,&b);
for (LL k=a;k<=b;k++)
ans+=find(k);
printf("%lld",ans);
return 0;
}
这个程序可以帮助我们做几件事。
- 直接作为代码提交可以拿到部分分。
- 可以用计算机暴力经过一段时间,以此求出所有情况,然后用分块的方法暴力来大大降低复杂度。例如,如果我们将17的数量以105分块,那么,我们的复杂度最大就是 2*105+104,也就是所有分块的求和,以及左右两端这两块的单独求和。那么,这个复杂度就相当的低了。因为int最大值为2147483647(231-1)
- 其次,这可以作为我们正确算法的一个验算的根据。在我们算法的正确性不能得到保障的情况下,经过多次大数据的验算是一个非常有效的验证算法正确性的方法。
所以,我们就开始思考正确方法。
我们知道,如果我们要判断一个数x有没有17,那么判断的次数应该是β(β=x的位数)。而对于a~b的数都要进行判断,很显然,这种方法是极其不优的。
那么,我们可以逆向思维。我们不选择计算一个一个数含有"17",而是反过来,我们枚举每两个相邻的位上的"17",并计算出在这两个位是"17"的情况下,有多少个数满足条件。而这样,我们就可以运用组合数学的方法进行进一步处理。
当然,补充一下,我们确定两位是"17"后,计算出[a,b]总共有多少个数满足这两位是"17",也可以扩展一下,就是[1,b]有多少个数满足这两位是"17",并用它减去[1,a-1]有多少个数满足这两位是"17"。
因此,我们不断枚举两个位是"17",并且计算出此时满足在[1,b]、[1,a-1]上的数的值并且不断做差。
那么,我们就可以通过这样的值进行求和。
不过,我们需要明确的是,对于一个数1717在[a,b]范围内,我们枚举百位、前位是"17"时,就会考虑到这个数1717,而在我们枚举个位、十位是"17"时,也会考虑到这个数1717,那么,它就被考虑了两次,正好与其1717有两个"17"相应,所以,我们可以很确定的知道,我们只需要枚举"17"的位置,而无需在意考虑的数是否还有别的"17",因为其如果还有别的"17",必然会在另外的枚举中被考虑到。
所以,我们可以写出如下代码。
#include<cstdio>
#define LL long long
using namespace std;
LL c[14];
LL ans,a;
LL find(LL );
int main()
{
c[0]=c[1]=1;
for (LL k=2;k<=13;k++)
c[k]=c[k-1]*10;
scanf("%lld",&a);
a--;
ans=find(a);
scanf("%lld",&a);
printf("%lld",find(a)-ans);
return 0;
}
LL find(LL x)
{
if (x<17)
return 0;
LL m=13,out=0;
while (c[m]>x)
m--;
m++;
LL y,now;
for (LL k=m;k>=3;k--)
{
y=x%c[k];
now=y/c[k-2];
out+=(x/c[k])*c[k-2];
if (now>17)
out+=c[k-2];
else
if (now==17)
out+=(k==3?1:((y%c[k-2])+1));
}
return out;
}