题解 | #F. 小红的零#
欢迎关注
F. 小红的零
整数末尾0的个数,取决于2和5的因子个数的最小值.
难点就在于:最小值
先来看2道基础题
对于一个数组arr, 给予一个x, 求
这题的思路,就是对arr进行排序,然后绝对值去掉,这样就划分为2个部分,一部分小于x,另一部分大于等于x
利用前缀和预处理,可以二分到分界点, 解决问题
求一个数组的所有子区间(2的因子个数)累加和
这是弱化版本,那其思路遍历右端点,然后累计
S(i) = S(i-1) + (i+1)*f(i)
最终S(i)的累加和
而这题的核心思路,其实上延续了类似思想
令f(x)为前x项的2因子的前缀和, g(x)为前x项的5因子的前缀和
对于某个区间[l, r]
令
则区间[l, r] 其贡献为
-
- 贡献为 g(r) - g(l - 1)
-
- 贡献为f(r) - f(l - 1)
本质上这题的巧妙之处在于
- 维护2因子多的区间(累加和,个数)
- 维护5因子多的区间 (累加和,个数)
而这个因子多少,是一个变动的过程,根据右端点来决定范围
-
大于z(x),为2因子多的区间
-
小于等于z(x), 则为5因子多的区间
所以这边采用4个树状数组
fw2, fwc2代表2因子的前缀和f(x),对应2因子的区间个数,
fw5, fwc5代表5因子的前缀和g(x),对应5因子的区间个数
而把 z(x) 作为 树状数组的 index-key
因为2因子和5因子个数,最多30n,考虑到负值,最多60n
引入offset作为偏移,不需要离散化
感觉这题思路还是挺绕的,可能直接看代码,更容易理解
感觉这题掺杂了 前缀和的前缀和
import java.io.*;
import java.util.*;
public class Main {
static class BIT {
int n;
long[] arr;
public BIT(int n) {
this.n = n;
this.arr = new long[n + 1];
}
void update(int p, long v) {
while (p <= n) {
this.arr[p] += v;
p += p&-p;
}
}
long query(int p) {
long res = 0;
while (p > 0) {
res += this.arr[p];
p -= p&-p;
}
return res;
}
}
static int split(int v, int b) {
int cnt = 0;
while (v % b == 0) {
v /= b;
cnt++;
}
return cnt;
}
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int n = sc.nextInt();
int mx = 60 * n; // 2^30 > 1e9, 因为绝对值的问题,所以30*2*n
int offset = 30 * n; // 引入offset,是因为这边没有离散化,而是做了一个偏移,平衡负值
BIT fw2 = new BIT(mx); // 2的因子累加和
BIT fwc2 = new BIT(mx); // 2的因子计数
BIT fw5 = new BIT(mx); // 5的因子累加和
BIT fwc5 = new BIT(mx); // 5的因子计数
long res = 0;
int acc2 = 0, acc5 = 0;
int diff = 0;
for (int i = 0; i < n; i++) {
// 前缀和为key
fw2.update(diff + offset, acc2);
fwc2.update(diff + offset, 1);
fw5.update(diff + offset, acc5);
fwc5.update(diff + offset, 1);
int v = sc.nextInt();
int n2 = split(v, 2);
int n5 = split(v, 5);
diff += (n2 - n5);
acc2 += n2;
acc5 += n5;
// 进行统计计算
long sum2 = fw2.query(mx) - fw2.query(diff + offset);
long cnt2 = fwc2.query(mx) - fwc2.query(diff + offset);
long sum5 = fw5.query(diff + offset);
long cnt5 = fwc5.query(diff + offset);
res += (acc2 * cnt2 - sum2) + (acc5 * cnt5 - sum5);
}
System.out.println(res);
}
}