2023牛客寒假算法基础集训营6 B-阿宁的倍数

题目描述:

给出一个长度为n的数组a,下标从1开始,进行q次操作。

修改操作:数组末尾增加一个数x,数组长度加1。

询问操作:有多少个i(i>x),满足ai是ax的倍数?

输入:

n,q。

a1,a2,…,ai(1<=i<=n)。

1<=n,q,ai<=2e5;

接下来q行查询,每行两个数op,x,代表一次操作。

如果op是1代表是修改操作;如果是2代表是询问操作。

如果是修改操作,1<=x<=2e5。

如果是询问操作,x小于等于当前数组长度。

输出:

对每一个询问操作,输出有多少ai是ax的倍数。

Solution:

考虑到如果ai是ax的倍数,则ai一定存在一个约数等于ax,所以对于已给出的数组a,我们可以从后往前枚举每个数的约数,将每种约数的个数用一个数组c来储存,当访问第i个数的时候,c[a[i]]即为所求,此处我们用一个数组s,令s[i]=c[a[i]]来存储对于原数组a的第i个位置,[i+1,n]中有多少个数能整除ai。

由于ai<=2e5,约数个数小于500,复杂度为O(500*2e5),故不会超时。

而在考虑操作1的x时,设p为操作1的次数,由于操作1的x是依此添加的,故我们不能照搬以上操作,以下我们对于两种操作2的询问情况进行考虑:

①1<=x<=n:

由于在操作1的添加是在原数组a之后的,故将添加进来的新数,我们只需要将其所有数的每种约数的个数用同样的数组c来储存,进行操作2时,我们只需要输出s[x]+c[a[x]]即使答案。

②n<x<=n+p时:

由于操作1的添加顺序,我们只能从前往后储存每种约数的个数,然而我们访问所求的是[x+1,n+p]中有多少个数存在约数等于a[x],此处我们考虑前缀和的做法,sum[p][j]表示在第p次添加后,约数j的个数为多少,这样在操作2时我们输出sum[p][a[x]]-sum[x][a[i]]即使[x+1,n+p]中能整除a[x]的个数。

但是我们实际操作发现,sum的两维都是2e5显然会爆空间,但是我们依旧要储存每次添加x后的状态,故此处我们转换一种方式,用一个vector<pair<int,int>>v[2e5],储存每种约数在第p次添加数时的个数,2e5储存约数种类,pair的first储存当前添加次数p,second储存当前约数的个数,

说得比较绕,此处举个例子:

第1次添加了一个数2后,其约数为1,2,p=1,则v[1].push_back({1,1}),v[2].push_back({1,1})

第2次添加了一个数4后,其约数为1,2,4,p=2,则v[1].push_back({2,2}),v[2].push_back({2,2}),v[4].push_back({2,1})。

由于在此过程中,我们已经用c储存了每种约数的个数,对于任意v[j]的second我们只需让其等于c[j]即可。

而在询问查找时,由于v的first一定有序,故我们只需要在v[a[x]]中二分查找到v[a[x]][mid].first==x,此时v[a[x]][mid].second等于是[n+1,n+x]中约数a[x]的个数,而我们要求的是[n+x+1,n+p]中约数a[x]的个数,而从刚刚前缀和思想中我们就可以简单得出,所求为v[a[x]][v.size()-1].second-v[a[x]][mid].second。

C++code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll>PLL;
const ll N = 4e5 + 10;
ll n, q;
ll a[N], s[N], c[N];
vector<PLL>v[N];
ll sech(ll pos, ll num) {//二分查询[n+x+1,n+p]中整除a[x]的个数
    ll k = v[num].size();
    ll l = 0, r = k - 1;
    while (l < r) {
        ll mid = l + r >> 1;
        if (v[num][mid].first >= pos)r = mid;
        else l = mid + 1;
    }
    return v[num][k - 1].second - v[num][r].second;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> q;
    ll p = 0;
    for (ll i = 1; i <= n; i++)cin >> a[i];
    for (ll i = n; i >= 1; i--) {//离线操作出原数组a中第i个数可以被[i+1,n]中多少个数整除
        s[i] = c[a[i]];
        for (ll j = 1; j * j <= a[i]; j++) {
            if (a[i] % j == 0) {
                c[j]++;
                if (j * j != a[i])c[a[i] / j]++;
            }
        }
    }
    memset(c, 0, sizeof c);
    while (q--) {
        ll op, x;
        cin >> op >> x;
        if (op == 1) {//操作1
            ++p;
            a[n + p] = x;

            for (ll j = 1; j * j <= x; j++) {
                if (x % j == 0) {
                    c[j]++;
                    v[j].push_back({ p,c[j] });//将每次添加后的约数状态存入vector
                    if (j * j != x) {
                        c[x / j]++;
                        v[x / j].push_back({ p,c[x / j] });
                    }
                }
            }
        }
        else {//操作2
            if (x >= 1 && x <= n) cout << s[x] + c[a[x]] << endl;
            else cout << sech(x - n, a[x]) << endl;
        }
    }
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值