P2602 [ZJOI2010]数字计数
题目链接
题目描述
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
输入格式:
输入文件中仅包含一行两个整数a、b,含义如上所述。
输出格式:
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。
输入样例:
1 99
输出样例:
9 20 20 20 20 20 20 20 20 20
说明
30%的数据中,a<=b<=10^6;
100%的数据中,a<=b<=10^12。
题解
算是数位DP吧。(虽然感觉没用到DP)
这个直接针对每一位来处理就好了。
对于第 i 位为数字 j 的情况,我们可以直接根据 x 推出,而相反,在windy数中的技巧在这里难以施展。
对于一个数字我们把它拆成三部分来考虑。
1.第 i 位前面的部分,记作pre
2.第 i 位,记作pi
3.第 i 位后面的部分,记作sub
例如 1390,i=2时 的三部分分别为13,9,0;i=4时,三部分分别为0,3,90
第 i 位为某个数字 j 的情况
①
j
<
p
i
:
t
i
m
e
s
=
(
p
r
e
+
1
−
[
j
=
0
]
)
∗
1
0
i
−
1
j<pi:times=(pre+1-[j=0])*10^{i-1}
j<pi:times=(pre+1−[j=0])∗10i−1 这一位小于 x 后面的低位随便,之前的范围是 0~pre,共 pre+1 个,但是当 j=0 时,pre 不能取到 0,否则就和之前的状态重复了。
②
j
=
p
i
:
t
i
m
e
s
=
(
p
r
e
−
[
j
=
0
]
)
∗
1
0
i
−
1
+
s
u
b
+
1
j=pi:times=(pre-[j=0])*10^{i-1}+sub+1
j=pi:times=(pre−[j=0])∗10i−1+sub+1 pre 同理,sub 的取值为 0 到 sub,共 sub+1 种情况。
③
j
>
p
i
:
t
i
m
e
s
=
(
p
r
e
−
[
j
=
0
]
)
∗
1
0
i
−
1
j>pi:times=(pre-[j=0])*10^{i-1}
j>pi:times=(pre−[j=0])∗10i−1
综上即可求解。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=15;
#define LL long long
LL ten[maxn],q[maxn],h[maxn],A,B,ans[maxn];
int p[maxn],n;
void DPS(LL x,LL d)
{
n=0;
for (LL y=x;y;y/=10) p[++n]=y%10;
if (!n) {ans[0]+=d;return;}
q[n+1]=0;for (int i=n;i>=1;--i) q[i]=q[i+1]*10+p[i];q[n+1]=1;
h[0]=0; for (int i=1;i<=n;++i) h[i]=h[i-1]+p[i]*ten[i-1];
for (int i=1;i<n;++i)
for (int j=0;j<10;++j)
{
LL pre=q[i+1]-(j>=p[i]?1:0),sub=h[i-1];
ans[j]+=(pre+(j>0))*ten[i-1]*d;
if (j==p[i]) ans[j]+=(sub+1)*d;
}
for (int j=1;j<=p[n];++j)
if (j==p[n]) ans[j]+=(h[n-1]+1)*d;else ans[j]+=ten[n-1]*d;
}
int main()
{
scanf("%lld%lld",&A,&B);*ten=1;
for (int i=1;i<=12;++i) ten[i]=ten[i-1]*10;
DPS(B,1);DPS(A-1,-1);
for (int i=0;i<9;++i) printf("%lld ",ans[i]);
printf("%lld\n",ans[9]);
return 0;
}