题目大意: 问位数为 n n n 的数(由 1 1 1 ~ 9 9 9 组成,并且要求不递减)中,有多少个数可以被 p p p 整除。
题解
这题 n n n大成这个鬼样,显然是要用数位 d p dp dp 了,但是,很要命的是,光是把每一位过一次就已经毫无疑问地TLE了,所以,只能从 p p p入手了。
这样看来,这道题里,数的组成不像一般的题一样,枚举每一位然后将每一位组合起来,于是,需要考虑其它方法去组合满足要求的数。
因为这个数的每一位是不递减的,所以,这个数其实可以由 1 1 1 ~ 9 9 9 个如同 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,... 这样的数相加得到,但是,因为需要保证这个数的位数等于 n n n,所以我们必定要选取 “ 1..1 ( n 1..1(n 1..1(n个 1 ) 1) 1)” 这个数。
然后又神奇地发现, 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,...它们 m o d p mod~p mod p的值是循环的1,如同一个无限循环小数,这里定义一个数组, s u m [ i ] sum[i] sum[i] 表示 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,...这些数里面有多少个数 m o d p = = i mod~p==i mod p==i,那么,只需要找到“循环节”,就可以快速统计出 s u m [ i ] sum[i] sum[i],于是再利用 s u m [ i ] sum[i] sum[i] 进行 d p dp dp 即可。
设
f
[
i
]
[
j
]
[
l
]
f[i][j][l]
f[i][j][l] 表示处理完
s
u
m
[
0
sum[0
sum[0 ~
i
−
1
]
i-1]
i−1],当前组成的数
m
o
d
p
mod~p
mod p 的值为
j
j
j,用了
l
l
l 个数进行组合。每次再枚举一个
u
u
u,表示选取
u
u
u 个
m
o
d
p
=
=
i
mod~p==i
mod p==i 的数,于是得到方程:
f
[
i
+
1
]
[
(
j
+
u
×
i
)
m
o
d
p
]
[
l
+
u
]
+
=
f
[
i
]
[
j
]
[
l
]
×
C
s
u
m
[
i
]
+
u
−
1
u
f[i+1][(j+u\times i)\bmod p][l+u]+=f[i][j][l]\times C_{sum[i]+u-1}^u
f[i+1][(j+u×i)modp][l+u]+=f[i][j][l]×Csum[i]+u−1u
经过实际测试后,发现这份代码 80 % 80\% 80% 的时间都是这个 C C C 贡献的(具体参见我可怜的TLE提交记录,虽然吸口氧还是可以过的),于是接下来对它进行一下优化。
仔细观察,发现 C s u m [ i ] + u − 1 u C_{sum[i]+u-1}^u Csum[i]+u−1u 只有 p × u p\times u p×u 种情况,也就是说,我只需要存下 5000 5000 5000 个组合数的值即可,但是 s u m [ i ] + u − 1 sum[i]+u-1 sum[i]+u−1 十分的大,如果用 m a p map map 存的话,时间复杂度依然突破天际,于是只能碰碰运气把它 h a s h hash hash一下了。
而且再观察一下,发现
C
s
u
m
[
i
]
+
u
−
1
u
C_{sum[i]+u-1}^u
Csum[i]+u−1u 中
u
u
u 是十分小的,于是对于
C
i
j
C_i^j
Cij,可以把它弄成这种形式:
C
(
i
,
j
)
=
i
!
j
!
(
i
−
j
)
!
=
(
i
−
j
+
1
)
∗
(
i
−
j
+
2
)
∗
.
.
.
∗
i
j
!
C(i,j)=\frac {i!} {j!(i-j)!}=\frac {(i-j+1)*(i-j+2)*...*i} {j!}
C(i,j)=j!(i−j)!i!=j!(i−j+1)∗(i−j+2)∗...∗i
显然后面那个式子的时间复杂度只有 O ( j ) O(j) O(j),在这题中,也就是 O ( 9 ) O(9) O(9),十分可观。
代码如下:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
using namespace std;
#define ll long long
#define mod 999911659ll
#define hash 973327
ll sum[550],k[550];
ll n,f[550][550][15];
int p;
ll c[1000010][10];
void work()//预处理sum数组
{
int tt=0,tot=0;
while(k[(tot*10+1)%p]==0&&tt<n)//寻找循环节
{
tot=(tot*10+1)%p;
k[tot]=++tt;
}
if(tt==n)//注意要特判一下还没找到循环节就到头了的情况
{
for(int i=0;i<p;i++)
sum[i]=k[i]!=0;
f[0][tot][1]++;//别忘了f数组的预处理
return;
}
int l=tt-k[(tot*10+1)%p]+1;
int kk=tot;
for(int i=1;i<=l;i++)//处理中间的循环节
{
kk=(kk*10+1)%p;
sum[kk]=(n-k[(tot*10+1)%p]+1)/l;
}
kk=0;//处理循环节前面的部分
for(int i=1;i<=k[(tot*10+1)%p]-1;i++)
sum[kk=(kk*10+1)%p]++;
kk=tot;//处理循环节后面的部分
for(int i=1;i<=(n-k[(tot*10+1)%p]+1)%l;i++)
sum[kk=(kk*10+1)%p]++;
f[0][kk][1]++;
}
ll ksm(ll x,ll y)
{
ll ans=1,tot=x;
while(y>0)
{
if(y%2==1)ans=ans*tot%mod;
y/=2;tot=tot*tot%mod;
}
return ans;
}
ll C(ll x,ll y)
{
if(x<y)return 1ll;
ll ans=1;
for(ll i=x-y+1;i<=x;i++)
ans=ans*(i%mod)%mod;
ll yy=1;
for(ll i=2;i<=y;i++)
yy=yy*i%mod;
return ans*ksm(yy,mod-2)%mod;//利用逆元做除法
}
int main()
{
scanf("%lld %d",&n,&p);
work();
for(int i=0;i<p;i++)
for(int j=0;j<=8;j++)
c[(sum[i]+j-1)%hash][j]=C(sum[i]+j-1,j);
for(int i=0;i<p;i++)
{
if(sum[i]==0)//假如没有mod p==i的数,就直接继承上一层的f数组
{
for(int l=1;l<=9;l++)
for(int j=0;j<p;j++)
f[i+1][j][l]=f[i][j][l];
continue;
}
for(int l=1;l<=9;l++)
for(int j=0;j<p;j++)
for(int u=0;u<=9-l;u++)
{
f[i+1][(j+u*i)%p][l+u]+=f[i][j][l]*c[(sum[i]+u-1)%hash][u];
f[i+1][(j+u*i)%p][l+u]%=mod;
}
}
ll ans=0;
for(int i=1;i<=9;i++)
ans=(ans+f[p][0][i])%mod;
printf("%lld",ans);
}
关于这个循环节存在性的证明——
因为 1 , 11 , 111 , 1111 , . . . 1,11,111,1111,... 1,11,111,1111,... 它们 m o d p mod~p mod p的值一定是 0 0 0 ~ p − 1 p-1 p−1 中的一个,所以,在 p p p 个数之内,一定会出现 m o d p mod~p mod p 的值相同的情况,那么此时,就出现了一个特殊的东西。
假设 x x x 个 1 m o d p 1~mod~p 1 mod p的值 与 y y y 个 1 m o d p 1~mod~p 1 mod p 的值 是相等的 ( x < y ) (x<y) (x<y),那么就说明存在一个数 11..100..0 11..100..0 11..100..0( y − x y-x y−x 个 1 1 1 和 x x x 个 0 0 0),这个数 m o d p mod~p mod p 的值一定是 0 0 0。那么也就是说,对于后面由比 y y y 个更多的 1 1 1 组成的数,其实他们前面的若干位 m o d p mod~p mod p 都是 0 0 0。 ↩︎