D - Kevin喜欢零(简单+困难)

170 阅读3分钟

题目链接

题目详情

本题分为简单版本和困难版本,二者唯一的区别是:简单版本有序列 a所有元素乘积1018 ≤10^{18} 的限制,困难版本没有。

氧气少年最近喜欢上了零。

给出一个长度为n(1n2105) n(1≤n≤2⋅10^5)的序列 a(1ai109)a (1≤a_i≤10^9),并且保证序列 a 所有元素乘积1018 ≤10^{18},求这个序列中满足如下条件的连续子段[al…ar] 的数量:

  • x=alal+1al+2arx=a_l⋅a_{l+1}⋅a_{l+2}…a_r,那么 x 的末尾恰好有 k(0k109)k(0≤k≤10^9)个零。

输入描述:

第一行包含一个整数 T(1T105)T(1≤T≤10^5),表示测试用例的组数。     对于每组测试用例:     第一行包含两个整数 n(1n2105)n(1≤n≤2⋅10^5)k(0k109)k(0≤k≤10^9),表示序列的长度和题目中提到的后导零的数量;     第二行包含 n 个整数 a1an(1ai109)a_1…a_n(1≤a_i≤10^9),表示该序列。

保证对于所有的测试用例,n 的总和不超过2105 2⋅10^5

输出描述:

对于每组测试用例:

仅输出一行,包含一个整数,表示答案。

示例

输入

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;
}