题目链接
题目详情
本题分为简单版本和困难版本,二者唯一的区别是:简单版本有序列 a所有元素乘积 的限制,困难版本没有。
氧气少年最近喜欢上了零。
给出一个长度为的序列 ,并且保证序列 a 所有元素乘积,求这个序列中满足如下条件的连续子段[al…ar] 的数量:
- 令 ,那么 x 的末尾恰好有 个零。
输入描述:
第一行包含一个整数 ,表示测试用例的组数。 对于每组测试用例: 第一行包含两个整数 和 ,表示序列的长度和题目中提到的后导零的数量; 第二行包含 n 个整数 ,表示该序列。
保证对于所有的测试用例,n 的总和不超过 。
输出描述:
对于每组测试用例:
仅输出一行,包含一个整数,表示答案。
示例
输入
2
5 3
125 1 8 1 1
1 0
6
输出
3
1
解题思路
这里我们直接给出困难版的解题思路(可以直接AC简单版),首先我们看到后导0的个数时,一定会想到找2和5的个数(因为两个数中如果一个含有若干个2或另一个含有若干个5,那么他们的乘积中就会出现0,而且0的个数是2和5个数的最小值)。
这道题,我们用前缀和处理每段区间的2和5的个数(O(1)的复杂度)。
接着我们就可以找符合条件的区间了,这里我们可以先枚举左端点,然后用用二分来找右端点。对于一个左端点,我们应该二分找两个右端点(符合条件的最靠近左端点的值、符合条件的最远离左端点的值)。
需要注意的是,每次二分结束后,我们需要判断这个区间的后导0的个数是否满足条件(==k),不满足的话枚举下一个左端点,满足将其累加。
AC代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
#include<cmath>
using namespace std;
#define int long long
#define endl '\n'
#define pair PII;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int N = 200010;
int s2[N], s5[N];//前缀和求2,5个数
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, k, a[N];
//int cnt2 = 0, cnt5 = 0;
cin >> n >> k;
//预处理前缀和
for (int i = 1; i <= n; i++)
{
cin >> a[i];
int c2 = 0, c5 = 0;
int t = a[i];
while (t % 2 == 0)
{
c2++;
t /= 2;
}
t = a[i];
while (t % 5 == 0)
{
c5++;
t /= 5;
}
s2[i] = s2[i - 1] + c2;
s5[i] = s5[i - 1] + c5;
}
//枚举左端点,二分右端点(上界和下界)
int res = 0;
for (int l = 1; l <= n; l++)
{
int l1 = l, r1 = n;//下界
while (l1 < r1)
{
int mid = (l1 + r1) / 2;
int t = min(s2[mid] - s2[l - 1], s5[mid] - s5[l - 1]);
if (t >= k) r1 = mid;
else l1 = mid + 1;
}
int t = min(s2[l1] - s2[l - 1], s5[l1] - s5[l - 1]);
if (t != k) continue;
int l2 = l, r2 = n;//上界
while (l2 < r2)
{
int mid = (l2 + r2 + 1) / 2;
int t = min(s2[mid] - s2[l - 1], s5[mid] - s5[l - 1]);
if (t <= k) l2 = mid;
else r2 = mid - 1;
}
t = min(s2[l2] - s2[l - 1], s5[l2] - s5[l - 1]);
if (t != k) continue;
res += l2 - l1 + 1;
}
cout << res << endl;
}
return 0;
}