题面:
给一个 n. 求[0,n] 有多少个数满足其阶乘的末尾上 有偶数个0.
n∈ [1,1e18].
题意 极为简洁.
正解也极为有意思(并非找规律).
重点: 如何将问题 与 数位DP相关.
将整个问题 分成小问题的话.
就是 当一个数满足 题设条件时. 它具有怎样的性质.
然后 就是去构造这个数.
一个数的阶乘有多少个 0 就是 阶乘的因子中有多少个5.
先看看如果是暴力的话怎么写.
int ans = 0;
for(int i=1;i<=n;i++) {
int he = 0,x;
for(int j=5;j<=i;j*=5) {
x = i/j;
he += x;
}
if(he%2 == 0) ans ++;
}
可以看一下 内层循环(j) 在五进制下是如何执行的.
假设当前数 i 的五进制为 ABCDE.
i = A *(5^4) + B * (5^3) + C * (5^2) + D * (5^1) + E * (5^0).
当 j = 5时.x = i/5 = ABCD. 类似二进制. 即将该五进制整体右移一位.
类似的
每一次的 x.
分别是.
ABCD. (j=5).
ABC. (j=25).
AB. (j=125).
A. (j=625).
此时 A 对于 he 的贡献 为 A*(625 + 125 + 25 + 5).
因为题目 只需要判断 he 的奇偶性.
那就看看 A贡献的奇偶性.
它分别受到 A本身的奇偶性 和 后面项数的奇偶性的影响.
因为 5的幂次方 必是奇数 所以只需要关心后面的项数.
即 A * (pos_A-1)的奇偶性. (pos_A为A所在5进制的位置).
类似的每个五进制位 都有其对应的贡献.
此时 这个数的he的奇偶性 已经和它本身的 5进制 有关系了.
那么 将题目再一次转化为:
在[0,n]的区间里 满足某个性质的 数字个数.
OK. 这样就和学过的数位DP挂上钩了.
该性质 即为 在该数的五进制上
(∑每一个数位的系数*(位数-1)) 为偶数.
数位DP就应该比较好写了.
#include <cstdio>
#include <cstring>
#define LL long long
LL dp[33][2],num[33],cot;
LL dfs(int pos,bool limit,bool lead,int pre) {
if(!pos) return !pre;
if(!limit && lead && ~dp[pos][pre]) return dp[pos][pre];
LL up = limit? num[pos]:4,res = 0;
for(int i=0; i<=up; i++) {
if(!lead) res += dfs(pos-1,limit && i == up,lead||i,(pre+i*(pos-1))&1);
else res += dfs(pos-1,limit && i == up,lead,(pre+i*(pos-1))&1);
}
if(!limit && lead) dp[pos][pre] = res;
return res;
}
LL solve(LL x) {
cot = 0;
while(x) num[++cot] = x%5,x /= 5;
return dfs(cot,1,0,0);
}
int main() {
memset(dp,-1,sizeof(dp));
LL n;
while(scanf("%lld",&n) && ~n) printf("%lld\n",solve(n));
return 0;
}
总结: 从题目发现了问题本身具有的性质. 然后利用这个性质 进行的 利用所学知识去求解.
利用五进制 得出符合条件数字的性质 也是这个题目最难也最有意思的部分.