题目描述:
给出一个长度为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;
}