Editorial for Cho kẹo hay bị ghẹo

Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.


Submitting an official solution before solving the problem yourself is a bannable offence.

Author: BJMinhNhut

Subtask 1

Mô phỏng lại trò chơi trong độ phức tạp ~O(NQ)~.

Subtask 2

Độ phức tạp ~O(QlogN)~.

Vì tất cả số đều chẵn, nên với mỗi query tất cả các viên kẹo trong đoạn ~[L, R]~ đều được bỏ vào giỏ. Như vậy bài toán của chúng ta trở thành tìm max của đoạn con cho trước.

  • Để tìm giá trị max trong đoạn ~[L, R]~, ta dùng CTDL Segment Tree.
  • Để tính số lượng giá trị ~val \le 10^5~ trong một đoạn ~[L, R]~, ta có thể làm như sau.
    • Gọi vector<int> pos[val] là tập hợp các vị trí của ~val~ trong mảng theo thứ tự tăng dần.
    • Số lượng phần tử có giá trị ~val~ trong đoạn sẽ bằng
      upper_bound(pos[val].begin(), pos[val].end(), R) - lower_bound(pos[val.begin(), pos[val].end(), L).

Subtask 3

Độ phức tạp ~O(QlogN)~.

Với mỗi số nguyên tố ~pr~ ta lưu vector<int> posPr[pr] là tập hợp các vị trí phần tử chia hết cho pr. Để tính mảng này, ta tiến hành phân tích các thừa số nguyên tố của ~A_i~ bằng sàng nguyên tố rồi cập nhật vào pos[val]. Lưu ý rằng với cách làm này, độ phức tạp không gian sẽ chỉ là ~O(nlog(A_i))~. Vì mỗi phần tử sẽ cập nhật cho tối đa ~O(log(A_i))~ thừa số nguyên tố.

Với tập vị trí của mỗi số nguyên tố ~p~, ta dựng một Segment Tree để tìm max các phần tử trong tập. Khi đó, để trả lời query, ta xét từng thừa số nguyên tố ~p~ của ~X~, rồi tìm phần tử lớn nhất chia hết cho ~p~ trong đoạn ~[L, R]~. Đáp án sẽ là giá trị lớn nhất trong các kết quả tìm được. Sau đó ta áp dụng cách đếm phần tử có giá trị cho trước trong đoạn ~[L, R]~ tương tự như sub 2.

Code trả lời query sẽ tương tự như sau:

int best = -1e9;
while (x > 1) {
    int p = pr[x];
    maximize(best, getMax(p, l, r));
    while (x%p == 0) x /= p;
}
if (best < 0) cout << "-1 -1\n";
else {
    int num = upper_bound(ALL(pos[best]), r) - lower_bound(ALL(pos[best]), l);
    cout << best << ' ' << num << '\n';
}

Code tham khảo:

// Created by BJMinhNhut
#include <bits/stdc++.h>
using namespace std;

#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define FOR(i, a, b) for(int i = (a), _b = (b); i <= _b; ++i) 
#define FORD(i, a, b) for(int i = (a), _b = (b); i >= _b; --i)
#define ALL(a) (a).begin(), (a).end()
#define RALL(a) (a).rbegin(), (a).rend()
#define MASK(i) (1ll<<(i))
#define BIT(t, i) (((t)>>(i))&1)
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef pair<ll, ll> pll;
typedef pair<int, int> ii;

/***Common Functions***/
template <class T>
bool minimize(T &a, T b) {
    if (a > b) {
        a = b;
        return true;
    }
    return false;
}

template <class T>
bool maximize(T &a, T b) {
    if (a < b) {
        a = b;
        return true;
    }
    return false;
}

/***End of Template***/
int n, q;
const int N = 1e5+5;
int a[N];
int pr[N];
vi pos[N];
vi posPr[N];
int par[2*N], siz[2*N];
class SegmentTree {
private:
    int n;
    vi node;

    void build(int p, int b, int e, vi &idx) {
        if (b == e) node[p] = a[idx[b]];
        else {
            int mid = (b+e) >> 1;
            build(p<<1, b, mid, idx);
            build((p<<1)|1, mid+1, e, idx);
            node[p] = max(node[p<<1], node[(p<<1)|1]);
        }
    }

    int getMax(int L, int R, int p, int b, int e) {
        if (e < L || R < b || L > R) return -1e9;
        if (L <= b && e <= R) return node[p];
        int mid = (b+e) >> 1;
        return max(getMax(L, R, p<<1, b, mid), getMax(L, R, (p<<1)|1, mid+1, e));
    }

public:
    void assign(int _n) {
        n = _n;
        node.assign(4*n+4, 0);
    }

    int getMax(int L, int R) {
        return getMax(L, R, 1, 0, n-1);
    }

    void build(vi &idx) {
        build(1, 0, n-1, idx);
    }
};

SegmentTree trees[N];

void sieve() {
    iota(pr, pr+N, 0);
    pr[0] = pr[1] = -1;
    for(int i = 2; i*i < N; i++) if (pr[i] == i) {
        for(int j = i*i; j < N; j += i)
            pr[j] = i;
    }
}

void Input() {
    cin >> n >> q;
    FOR(i, 1, n) cin >> a[i];
}

int getMax(int p, int l, int r) {
    int treeL = lower_bound(ALL(posPr[p]), l) - posPr[p].begin();
    int treeR = upper_bound(ALL(posPr[p]), r) - posPr[p].begin()-1;
    return trees[p].getMax(treeL, treeR);
}

void prepare() {
    sieve();
    FOR(i, 1, n) pos[a[i]].pb(i);
    FOR(i, 1, n) {
        int tmp = a[i];
        while (tmp > 1) {
            int p = pr[tmp];
            posPr[p].pb(i);
            while (tmp%p == 0) tmp /= p;
        }
    }
    FOR(i, 1, N-1) if (posPr[i].size()) {
        trees[i].assign(posPr[i].size());
        trees[i].build(posPr[i]);
    }
}

void Solve() {
    prepare();
    while (q--) {
        int x, l, r; cin >> x >> l >> r;
        int best = -1e9;
        while (x > 1) {
            int p = pr[x];
            maximize(best, getMax(p, l, r));
            while (x%p == 0) x /= p;
        }
        if (best < 0) cout << "-1 -1\n";
        else {
            int num = upper_bound(ALL(pos[best]), r) - lower_bound(ALL(pos[best]), l);
            cout << best << ' ' << num << '\n';
        }
    }
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    Input(), Solve();
    return 0;
}

Comments

Please read the guidelines before commenting.


There are no comments at the moment.