[UVA12683] Odd and Even Zeroes
Description
给出 \(n\),求出 \(0!, 1!, 2! \ldots, n!\) 中有几个末尾有偶数个 \(0\)。
\(1\le n\le 10^{18}\)。
Solution
根据基本结论,一个数末尾 \(0\) 的个数等于该数有几个因数 \(5\)。而一个数的阶乘末尾有几个 \(0\),等于小于等于该数的所有数的因数 \(5\) 的个数的和。对此,我们设 \(d_i\) 表示有多少个数满足 \(5^i\) 是其因子,但 \(5^{i+1}\) 不是。那么考虑 \(n\) 的五进制,发现 \(d_i\) 即为对应位上的值。而 \(n!\) 末尾的 \(0\) 的个数,可以用 \(\sum\limits_{i=0}^{len} d_i\times i\) 来表示。
而我们要使其为偶数,只需要 \(i\) 是奇数的 \(d_i\) 之和为偶数即可。因此考虑数位 \(dp\),设 \(f_{i,0/1,0/1}\) 表示当前到了第 \(i\) 位,之前是否取最大值,奇数位上的和的情况(\(0\) 表示偶数,\(1\) 表示奇数)。转移就枚举当前这一位的数是什么,然后更新即可。
Code
#include<cstdio>
#include<cstring>
#define N 30
#define ll long long
using namespace std;
int len,a[N];
ll n,f[N][2][5*N];
ll dp(int x,int tp,int s)
{
if (x<0) return s==0;
if (f[x][tp][s]) return f[x][tp][s];
int mx=tp?a[x]:4;ll res=0;
for (int i=0;i<=mx;++i)
{
if (x&1) res+=dp(x-1,tp&&(i==mx),(s+i)%2);
else res+=dp(x-1,tp&&(i==mx),s);
}
return f[x][tp][s]=res;
}
int main()
{
while (true)
{
scanf("%lld",&n);
if (n==-1) break;
memset(f,0,sizeof(f));
len=0;
ll x=n;
while (x)
{
a[len++]=x%5;
x/=5;
}
len--;
printf("%lld\n",dp(len,1,0));
}
return 0;
}