Solution
听说这题要用数位dp。
不会。
只能用暴力了…
举个例子:求
[
29
,
3246
]
[29,3246]
[29,3246]中每个数码的出现次数。
首先想到把每个数码分开求。
好像很难。
然后想到
[
1
,
3246
]
[1,3246]
[1,3246]的答案减去
[
1
,
28
]
[1,28]
[1,28]的答案。
好像还是很难。
最后突发奇想,把位数也分开,例如:求
[
1
,
3246
]
[1,3246]
[1,3246]中十位出现
4
4
4的个数。
好像很水。
还是刚才那个例子,显然,从 1 1 1开始的每 100 100 100个数中,就有 10 10 10个数的十位是 4 4 4, 3246 / 100 ∗ 10 = 320 3246/100*10=320 3246/100∗10=320,已经有 320 320 320个十位是 4 4 4的数了。那么剩下的 46 46 46个数呢?显然,从 1 1 1开始的每 100 100 100个数中,十位是 4 4 4的数都是第 40 40 40个到第 49 49 49个, 3246 % 100 3246\%100 3246%100= 46 46 46,剩下 46 46 46个数,第 40 40 40个数到第 46 46 46个数符合条件,也要算进去。
然后会发现问题来了: 0 0 0的个数好像多出来了?是的,例如计算 [ 1 , 3246 ] [1,3246] [1,3246]中十位出现 0 0 0的个数,像刚才那样,每 100 100 100个数中就有 10 10 10个数的的十位是 0 0 0,是这 100 100 100个数中的第 1 1 1个数到第 9 9 9个数,好像哪里不对? [ 1 , 9 ] [1,9] [1,9]没有十位的0啊?
可以看出,刚才计算某一位某个数码的出现次数,并没有管比这一位高的几位是几,也就是说可能全部都是 0 0 0。实际上在刚才的方法中,将 [ 1 , 3246 ] [1,3246] [1,3246]全部看作了四位数: 0001 0001 0001, 0002 0002 0002, 0003 0003 0003…
那么,如何减去这些前导零呢?很简单, [ 1 , 9 ] [1,9] [1,9]每个数有 3 3 3个前导零, [ 10 , 99 ] [10,99] [10,99]每个数有 2 2 2个前导零,以此类推。
计算部分长这样(不包括减去前导零):
for(i=0;i<=cnt;i++)//枚举位数,位数的标号从0开始,便于用p[0]对应个位
for(j=0;j<=9;j++)//枚举数码
{
num[j]+=x/p[i+1]*p[i];//num[j]记录j的出现次数
mod=x%p[i+1];//p[i]为10的i-1次方
if(mod<p[i]*j)continue;//x为区间右端点
if(mod>p[i]*(j+1)-1)mod=p[i]*(j+1)-1;
num[j]+=(mod-p[i]*j+1);
}
观察上面的代码,会发现问题又来了:计算 [ 1 , 3246 ] [1,3246] [1,3246]中十位为 0 0 0的个数,和上面一样,剩下 46 46 46个数, m o d = 46 mod=46 mod=46, j = 0 j=0 j=0, m o d − p [ i ] ∗ j + 1 = 47 mod-p[i]*j+1=47 mod−p[i]∗j+1=47,不是只有 46 46 46个吗?
进一步发现,这段代码中,每一位的 0 0 0都会多算一个,可以看成:把 0 0 0这个数以及它的前导零都多算了一次,也就是求了 [ 0 , 3246 ] [0,3246] [0,3246]每一位 0 0 0的出现次数,那么应减去 [ 0 , 3246 ] [0,3246] [0,3246]的前导零。
那么问题又来了, [ 0 , 28 ] [0,28] [0,28]中算了 0 0 0, [ 0 , 3246 ] [0,3246] [0,3246]中也算了 0 0 0,相减不就抵消了吗?为什么还要各自减去呢?
这个问题将在以下代码中讲解:
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
long long p[200],a,b,ans[200],x,mod,y,t,num[200],i,j,cnt,tot;
int main()
{
p[0]=1;
cin>>a>>b;
x=b;
//先处理[1,b]每个数码1~9的出现次数,[0,b]0的出现次数
while(b)
{
cnt++;
b/=10;
}
cnt--;
//p[0]对应个位,p[0]~p[cnt]对应b的每一位,总共cnt+1位
for(i=1;i<=cnt+1;i++)
p[i]=p[i-1]*10;//p[i]表示10的i次方
for(i=0;i<=cnt;i++)//第i位
for(j=0;j<=9;j++)//数字j,出现几次
{
ans[j]+=x/p[i+1]*p[i];
//每p[i+1]个连续整数中,这一位必会出现p[i]次j
mod=x%p[i+1];//个数不足p[i+1]的段,可能还出现j
if(mod<p[i]*j)continue;
//长度不足p[i]*j说明剩下的段中不存在第i位为j的
//这句不懂的用这段代码模拟一下[1,3426]中十位为4的个数的计算过程
if(mod>p[i]*(j+1)-1)mod=p[i]*(j+1)-1;
//把剩余的段中这一位没有j的去掉
//这句不懂的用这段代码模拟一下[1,3466]中十位为4的个数的计算过程
ans[j]+=mod-p[i]*j+1;//[p[i]*j,mod]的数中,这一位都为j
}
for(i=0;i<cnt;i++)//减去前导零
{
t=p[i];
if(i==0)t=0;//位数为i+1的最小非负整数为t,因为要减掉0,所以是非负
y=p[i+1]-1;//位数为i+1的最大整数为y
if(y>x)y=x;//y不能超过x,即不能超过b
ans[0]-=(cnt-i)*(y-t+1);//把所有数都按b的位数来看,减去前导零的个数
//[t,y]的每个数都会有cnt-i个前导零
}
x=a-1;
//处理[1,a-1]数码1~9的出现次数,以及[0,a-1]0的出现次数
tot=a-1;
cnt=0;
if(tot==0)cnt=1;
while(tot)
{
cnt++;
tot/=10;
}
cnt--;
for(i=0;i<=cnt;i++)
for(j=0;j<=9;j++)
{
num[j]+=x/p[i+1]*p[i];
mod=x%p[i+1];
if(mod<p[i]*j)continue;
if(mod>p[i]*(j+1)-1)mod=p[i]*(j+1)-1;
num[j]+=(mod-p[i]*j+1);
}
/*
讲解刚才那个问题:
刚才说会把0给算进去,但是两个区间中都会算到0,相减是不是就能抵消了?
对于这个程序来说,如果a-1是一位数,那么cnt=0,下面的那个循环不会进入,
这个区间内的0不会被算;b不是一位数,上面那个区间的0会被算,就多出来了。
所以在处理两个区间时,各自减掉各自的0就行了。
*/
if(cnt!=0)
for(i=0;i<cnt;i++)
{
t=p[i];
if(i==0)t=0;
y=p[i+1]-1;
if(y>x)y=x;
num[0]-=(cnt-i)*(y-t+1);
}
for(i=0;i<=9;i++)//两个区间相减,得到[a,b]的结果
cout<<ans[i]-num[i]<<" ";
return 0;
}
注意要开long long,不然
30
30
30分,当然不用全开long long,只是因为我懒得去想哪些不用开。
历经千辛万苦,终于利用暴力解出一道数位dp。
于是,我至今不会数位dp。