CF818E Card Game Again

题目大意

给定长为 n n n 的数列 a a a,求有多少对 ( x , y ) (x,y) (x,y) 满足 k k k ∏ i = x i + y ≤ n a i \prod\limits_{i=x}^{i+y\le n}a_i i=xi+ynai 的约数。

解题思路

做法一

我会 二分 !!!

枚举左端点,然后去找满足条件的右端点。

首先将 k k k 分解质因数。

可以发现, a i a_i ai 分解质因数后,只有和有 k k k 相同的质因数可以选上,否则对于答案是没有贡献的。

那么就可以将有用的质因数的个数用前缀和维护起来。

可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间复杂度内求出所有的对整除影响的质数有多少。

在这个区间内,若对于每个质数的个数均大于等于 k k k 分解后对应质数的个数,则代表这个区间可以整除 k k k

时间复杂度为 O ( n log ⁡ n log ⁡ k + k ) O(n\log n\log k+\sqrt k) O(nlognlogk+k )

CODE

#include <bits/stdc++.h>

using namespace std;

inline int read()
{
    char c = getchar();
    int x = 0, f = 1;
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = x * 10 + c - '0', c = getchar();
    return x * f;
}

#define int long long

#define mk(x, y) make_pair(x, y)

const int MAXN = 1e5 + 5;

vector<pair<int, int>> vec;

int a[MAXN], n, k;

int cnt[MAXN][50], ans;

bool Check(int l, int r)
{
    for (int i = 0; i < vec.size(); i++)
        if (cnt[r][i] - cnt[l - 1][i] < vec[i].second)
            return 0;
    return 1;
}

signed main()
{
    n = read(), k = read();
    a[n + 1] = k;
    for (int i = 1; i <= n; i++)
        a[i] = read();
    for (int i = 2; i * i <= k; i++)
    {
        if (k % i == 0)
            vec.push_back(mk(i, 0));
        while (k % i == 0)
        {
            k /= i;
            vec[vec.size() - 1].second++;
        }
    }
    if (k != 1)
        vec.push_back(mk(k, 1));
    for (int i = 1; i <= n + 1; i++)
    {
        int tmp = a[i];
        for (int j = 0; j < vec.size(); j++)
        {
            cnt[i][j] = cnt[i - 1][j];
            while (tmp % vec[j].first == 0)
            {
                tmp /= vec[j].first;
                cnt[i][j]++;
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        int l = i, r = n + 1, as = 0;
        while (l <= r)
        {
            int mid = (l + r) >> 1;
            if (Check(i, mid))
                r = mid - 1, as = mid;
            else
                l = mid + 1;
        }
        ans += n - as + 1;
    }
    printf("%lld\n", ans);
    return 0;
}
做法二

我会乱推!!!

显然,如果有一个区间 [ l , r ] [l,r] [l,r] 中间的所有数乘积能被 k k k 整除,则对于一个 a ≤ l , b ≤ r a \leq l,b \leq r al,br 的一切区间 [ a , b ] [a,b] [a,b],里面所有数的乘积一定都能被 k k k 整除。

那么我们只需要找出所有的 最小合法区间 即可。

1 → n 1\to n 1n 进行前缀乘积,每次模上 k k k,只要遇到一个合法区间,也就是前缀乘积为 0 0 0,这时候从这个点立刻回头,从后往前找出这个区间的左端点,找到的区间肯定是最短的。

另外,要避免重复的情况,当开始搜下一个新的区间时候,应当把起始点重置为之前找到的右端点 + 1 +1 +1

时间复杂度 O ( n log ⁡ k ) \mathcal O(n \log k ) O(nlogk)

CODE

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int _ = 1e5 + 5;

inline int read()
{
    char c = getchar();
    int x = 0, f = 1;
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = x * 10 + c - '0', c = getchar();
    return x * f;
}

int n, k;

int a[_];

int now = 1, l = 1, r;

int ans;

signed main()
{
    n = read();
    k = read();
    for (int i = 1; i <= n; ++i)
    {
        a[i] = read();
        now = now * a[i] % k;
        if (!now)
        {
            now = 1;
            for (r = i; now * a[r] % k; r--)
                now = now * a[r] % k;
            ans += (n - i + 1) * (r - l + 1);
            l = r + 1;
        }
    }
    printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值