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

文章描述了一种数组操作问题,涉及修改和询问操作。在每次询问时,需要找出数组中大于指定值x的数中,能被ax整除的个数。解决方案包括使用前缀和和约数统计,优化空间复杂度以适应大数操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

给出一个长度为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;
}
### 关于2020年寒假算法基础集训营中的欧几里得算法 在2020年的寒假算法基础集训营中,确实存在涉及欧几里得算法的相关题目。具体来说,在第四场竞赛的第一题即为“A. 欧几里得”,该题目的核心在于利用扩展欧几里得定理来解决问题[^5]。 #### 扩展欧几里得算法简介 扩展欧几里得算法主要用于求解形如 ax + by = gcd(a, b) 的线性不定方程的一组特解(x,y),其中gcd表示最大公约数。此方法不仅能够计算两个整数的最大公因数,还能找到满足上述条件的具体系数x和y。 对于给定的数据范围较小的情况可以直接通过递归来实现;而对于较大数据则需考虑效率优化问题。下面给出了一段基于C++语言编写的用于解决此类问题的模板代码: ```cpp #include<bits/stdc++.h> #define int long long using namespace std; // 定义全局变量存储结果 int x, y; void ex_gcd(int a, int b){ if(b == 0){ x = 1; y = 0; return ; } ex_gcd(b, a % b); int tmp = x; x = y; y = tmp - (a / b) * y; } ``` 这段程序实现了经典的扩展欧几里得算法逻辑,并且可以作为处理类似问题的基础工具函数调用。 #### 实际应用案例分析 回到原题本身,“A. 欧几里得”的解答思路就是先预处理斐波那契数列前若干项数值存入数组`a[]`内以便快速查询,之后针对每一次询问直接输出对应位置处两相邻元素之和即可得出最终答案。这实际上巧妙运用到了广为人知的裴蜀定理——任意一对互质正整数都可由它们自身的倍数组合而成,而这里正是借助了这一性质简化了解决方案的设计过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值