Editorial
Group 1: , , , in each query
In this group, the straightforward solution would pass, so let's just maintain a multiset of elements and for each query of 3 type just iterate through each pair of elements and check if .
int n; cin >> n; multiset<int> ms; for (int i = 0; i < n; i++) { int t; cin >> t; ms.insert(t); } int q; cin >> q; while (q--) { int t; cin >> t; if (t == 1) { int x; cin >> x; ms.insert(x); } else if (t == 2) { int x; cin >> x; ms.erase(ms.find(x)); } else { int x; cin >> x; long long cnt = 0; for (auto a: ms) { for (auto b: ms) { cnt += (a / b) == x; } } cout << cnt << endl; } }
Time complexity: , or using hashmap like multiset
Group 2: , in each query
In this group we can notice that the number of different elements is not so big so let's maintain an array - which will store the number of occurrences of . Let's notice that from goes so the good for our are the numbers on a segment . So now we can iterate through each and add to our answer the number of such elements that so it is just . To achieve it, we can use either Segment Tree, or Binary-Indexed Tree, or just after each query of 1st and 2nd type recalculate prefix sums.
Time complexity:
Memory complexity:
Group 3,4: There are no queries of the -nd type
For this and all the following groups, we need to notice the next fact:
if , then either or .
Let's call the biggest as . So for the solution, we will consider these two cases separately.
For we already know that so we can simply solve it as previous group.
Now, for first observation would be that the number of such 's is not so big . Let's store additional array which will represent the answer for small . So the question is, how can we maintain it? It turns out that it is not so hard. Assume that we have got query with insertion an element . Now we have two options. 1st is , 2nd is . Both of this variation could be calculated for each in at most using some data structure like Segment Tree, or Binary-Indexed Tree.
Time complexity:
Memory complexity:
Group 5 Here we should do it in both ways really carefully. Also the problem could be solved faster without extra log, because an update query we will perform only times, and get query . So it would be nice for us to answer update query in and get in . It could be done using sqrt decomposition.
//#pragma GCC optimize("Ofast") //#pragma GCC target("avx,avx2,fma") //#pragma GCC optimization ("unroll-loops") //#pragma GCC target("avx,avx2,sse,sse2,sse3,sse4,popcnt") #include <iostream> #include <vector> #include <cstring> using namespace std; #define elif else if const int block_size = 320; const int max_n = 1e5 + 1; int blocks_sum_pref[max_n / block_size + 5]; int block_pref[max_n]; void init() { memset(blocks_sum_pref, 0, sizeof(blocks_sum_pref)); memset(block_pref, 0, sizeof(block_pref)); } int bget(int l, int r) { if (l == (l / block_size) * block_size) return block_pref[r]; return block_pref[r] - block_pref[l - 1]; } int get(int l, int r) { if (l > r) return 0; int lid = l / block_size; int rid = r / block_size; if (lid == rid) { return bget(l, r); } int res = bget(l, (lid + 1) * block_size - 1); res += bget(rid * block_size, r); res += blocks_sum_pref[rid - 1] - blocks_sum_pref[lid]; return res; } void add(int pos, int val) { int pid = pos / block_size; for (int i = pid; i < max_n / block_size; i++) { blocks_sum_pref[i] += val; } for (int i = pos; i < min(max_n, (pos / block_size + 1) * block_size); i++) { block_pref[i] += val; } } long long cnt[block_size + 5]; int read_int() { int x; cin >> x; return x; } signed main() { init(); memset(cnt, 0, sizeof(cnt)); auto insert = [&](int x) -> void { add(x, 1); int min_val; int max_val; for (int i = 1; i <= block_size; i++) { // a / x min_val = x * i; max_val = x * (i + 1) - 1; if (min_val >= max_n) break; max_val = min(max_val, max_n - 1); cnt[i] += get(min_val, max_val); } for (int i = 1; i <= block_size; i++) { // x / a max_val = x / i; min_val = x / (i + 1) + 1; if (i == 1) { max_val--; } if (max_val == 0) break; if (min_val <= max_val) cnt[i] += get(min_val, max_val); } }; auto erase = [&](int x) -> void { int min_val; int max_val; for (int i = 1; i <= block_size; i++) { // a / x min_val = x * i; max_val = x * (i + 1) - 1; if (min_val >= max_n) break; max_val = min(max_val, max_n - 1); cnt[i] -= get(min_val, max_val); } for (int i = 1; i <= block_size; i++) { // x / a max_val = x / i; min_val = x / (i + 1) + 1; if (i == 1) { max_val--; } if (max_val == 0) break; if (min_val <= max_val) cnt[i] -= get(min_val, max_val); } add(x, -1); }; auto count = [&](int x) -> long long { if (x <= block_size) { return cnt[x]; } long long res = 0; for (int i = 1; i <= min(max_n - 1, max_n / block_size + 5); i++) { if (get(i, i) == 0) continue; int min_val = x * i; int max_val = min_val + i - 1; if (min_val >= max_n) break; max_val = min(max_val, max_n - 1); res += get(min_val, max_val) * get(i, i); } return res; }; int n = read_int(); for (int i = 0; i < n; i++) { insert(read_int()); } int q; cin >> q; int t, x; while (q--) { t = read_int(); x = read_int(); if (t == 1) { insert(x); } elif (t == 2) { erase(x); } else { cout << count(x) << endl; } } }