题目
第一行有一个正整数T,表示测试数据的组数。
接下来的T行,每行输入两个十进制整数n和base。
对于每组数据,输出一个十进制整数,表示在base进制下,n!结尾的零的个数。
2
10 10
10 2
2
8
对于20%的数据,n<=20,base<=16
对于50%的数据,n<=10^9,base<=10^5
对于100%的数据,1<=T<=50,0<=n<=10^18,2<=base<=10^12
题解
由进制转化的过程(不停除以进制数)可以很容易地把问题转化为n!可以除base的次数
但是n和base如此之大,以至于如果想求出阶乘然后一次一次除会爆炸
于是题解就把base分解质因数为
a1k1∗a2k2∗...∗am∗km
然后检查每个质因数ai在n!中的贡献 cnt,于是就可以得出这个质因数最多容纳cnt/ki个base。
把所有容纳能力取个最小值即为答案。
问题在于我们在求ai在n!中的贡献时,可能需要O(nlogn)的时间:
枚举1*2*3*…*n中的每一个数j,易得ai在j中的贡献,累计所有贡献即为ai在n!中的贡献。
如果采用上述办法,时间会超限。
对于每个质因数,给n一直除a[i],并将每一次的商加起来,即为答案。
时间复杂度
O(logn)
参考:http://blog.youkuaiyun.com/hiweibolu/article/details/52658317
注意,因为数据很大,对于n,m,ans,记录质因数的数组,用来暂时作为n除a[i]的变量,Pascal 用int64,C++long long会爆
代码
var
t,i,j,s:longint;
n,m,ans,k,d:int64;
a:array[0..1000000]of longint;
b:array[0..1000000]of int64;//记录质因数小心炸范围!
begin
readln(t);
for i:=1 to t do
begin
readln(n,m);
fillchar(a,sizeof(a),0);
ans:=0;s:=0;
k:=m;
for j:=2 to trunc(sqrt(m)) do
if (k>0)and(k mod j=0) then
begin
inc(s);
b[s]:=j;
while (k>0)and(k mod j=0) do
begin
inc(a[s]);
k:=k div j;
end;
end;
if k>1 then
begin
inc(s);b[s]:=k;a[s]:=1;
end;//可能会剩下一个比√m大的质数
for j:=1 to s do
begin
k:=n;d:=0;
while (k>0) do
begin
k:=k div b[j];
d:=d+k;
end;
if (ans=0)or(ans>d div a[j]) then ans:=d div a[j];
end;
writeln(ans);
end;
end.