Hướng dẫn cho Dãy số (Contest Practice VNOI 2021 Round 7)


Chỉ sử dụng khi thực sự cần thiết như một cách tôn trọng tác giả và người viết hướng dẫn này.

Chép code từ bài hướng dẫn để nộp bài là hành vi có thể dẫn đến khóa tài khoản.

Authors: Flower_On_Stone

Subtask 1: \(a = 1\)

Điều kiện \(a_L+a_{L+1}+...+a_{H}\ge S \Leftrightarrow H-L+1 \ge S \Leftrightarrow L \ge H-S+1\). Ta cần tìm \(L\) để \(P=b_L+b_{L+1}+...+b_H\) đạt min. Đặt sumB(i) là tổng tiền tố độ dài \(i\) của mảng \(b\), như vậy thì \(P=sumB_H-sumB_{L-1}\). Bài toán quy về tìm \(L\) có \(sumB_{L-1}\) lớn nhất.

Mỗi lần thực hiện truy vấn loại 1, tăng mảng sumB trong đoạn \([i,n]\) (từ vị trí i tới vị trí n) lên delta đơn vị. Truy vấn loại 2 là tìm sumB lớn nhất trong đoạn liên tiếp (ở đây là đoạn tiền tố). Có thể thực hiện việc này bằng kĩ thuật lazy update trên Segment tree.

Subtask 2: \(b = 1\)

Để \(P\) đạt min thì \(L\) phải lớn nhất có thể. Đặt sumA(i) là tổng tiền tố độ dài \(i\) của mảng \(a\), như vậy phải có \(sumA_H-sumA_{L-1} \ge S \Leftrightarrow sumA_{L-1} \le sumA_H-S\). Có thể dùng kĩ thuật như ở subtask 1 để thực hiện truy vấn loại 1, tại mỗi nút trên cây ST ta lưu sumA nhỏ nhất trong đoạn mà nó quản lý. Sau đó ta sẽ dùng kĩ thuật Tìm kiếm nhị phân trên Segment tree (Segment tree walk). Ta sẽ đi vào các nút có min_sumA \(\le sumA_H-S\) (sẽ tồn tại vị trí thỏa mãn trong nút này), ưu tiên xuống nhánh con phải hơn so với nhánh trái để \(L\) lớn nhất.

Subtask 3:

Giới thiệu với các bạn ý tưởng này, thực tế trong bộ test không có subtask 3.

Các truy vấn loại 2 nằm sát nhau nên ta có thể thực hiện mỗi truy vấn loại 1 nằm trước trong \(O(1)\). Phần còn lại là trả lời truy vấn loại 2.
Sử dụng 2 mảng sumA, sumB như subtask 1 & 2, cần tìm vị trí \(L \le H\) thỏa \(sumA_{L-1} \le sumA_H-S (*)\) và \(sumB_{L-1}\) đạt max.

Để quản lý điều kiện \(( * )\), tại mỗi nút trên cây phân đoạn ta sẽ lưu một cây Trie. Cây Trie giúp giải quyết bài toán: "Cho một tập hợp chứa các cặp \((a,b)\), trong những cặp có \(a \le V\) thì \(b\) lớn nhất là bao nhiêu?"

Vì chỉ truy vấn tiền tố nên có thể dùng Fenwick Tree để đơn giản hơn.

Phân tích độ phức tạp không gian & thời gian :

  • Sau khi tính xong các truy vấn loại 1 “có nghĩa” (nằm trước những truy vấn loại 2), với mỗi vị trí \(i\), ta sẽ cập nhật vào \(O(log_2(n))\) nút có phạm vi quản lý bao chứa vị trí \(i\). Tại mỗi nút, ta sẽ thêm cặp \((sumA_{i-1},sumB_{i-1})\) vào cây Trie của nút đấy. Vì Trie cài bằng con trỏ nên bộ nhớ dùng thêm tối đa là \(log_2(sumA) \le log_2(1014) = 46.5069933...\) cấu trúc nút của Trie. Do đó tổng bộ nhớ cấp phát là \(O(n.log_2(n).log_2(sumA))\).
  • Với mỗi truy vấn loại 2, ta sẽ đi “hỏi” vào \(O(log_2(n))\) nút có phạm vi quản lý nằm trong đoạn \([1,i]\). Tại mỗi nút ta sẽ thực hiện truy vấn trên cây Trie của nút đấy với độ phức tạp \(log_2(sumA)\). Vậy tổng thời gian thực hiện của 2 loại truy vấn cũng là \(O(n.log_2(n).log_2(sumA))\).

Subtask 4:

Thực hiện chia căn, phân rã mảng A thành các block. Mỗi block có kích thước là \(T\) phần tử, như vậy có \(\lceil n/T \rceil\) block. Giả sử block gồm từ vị trí \(L\) tới vị trí \(R\), với mỗi \(i : L \le i \le R\) ta lưu sa[i], sb[i] lần lượt là tổng các A,B từ \(i\) tới \(R\). Sau đấy sắp xếp các vị trí \(i\) theo sa tăng dần và lấy min dồn của sb.

  • Khi update chỉ thay đổi trong block chứa vị trí cần xét hết \(O(T.log_2)\)
  • Khi query ta đi từ block chứa vị trí hiện tại về block đầu tiên, xong tìm kiếm nhị phân và lấy sb nhỏ nhất. Đi qua block nào thì ta cộng toàn bộ A và B của block đấy vào biến nhớ. \(O((n/T).log_2)\)

Độ phức tạp \(O(q * (T.log_2+(n/T).log_2))\). Chọn \(T = [\sqrt{n}]\) thì ĐPT tốt nhất.

Source code:

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 7;
int n;
long long S;
pair<long long, long long> a[N];

template <typename T>
inline void minimize(T &x, T y)
{
    if (x > y)
        x = y;
}

template <typename T>
inline pair<T, T> operator+(const pair<T, T> &x, const pair<T, T> &y)
{
    return make_pair(x.first + y.first, x.second + y.second);
}

template <typename T>
inline pair<T, T> operator-(const pair<T, T> &x, const pair<T, T> &y)
{
    return make_pair(x.first - y.first, x.second - y.second);
}

template <typename T>
void operator+=(pair<T, T> &x, const pair<T, T> &y)
{
    x = x + y;
}

template <typename T>
void operator-=(pair<T, T> &x, const pair<T, T> &y)
{
    x = x - y;
}

struct GroupDecomposition
{
    const int BLOCK_SIZE = 316;
    int SIZE, NUM_BLOCK;
    vector<pair<long long, long long>> org, bucket, sum;
    void init()
    {
        SIZE = n;
        NUM_BLOCK = SIZE / BLOCK_SIZE + bool(SIZE % BLOCK_SIZE);
        org.resize(SIZE);
        bucket.resize(SIZE);
        sum.resize(NUM_BLOCK);
        for (int i = 0; i < SIZE; ++i)
        {
            org[i] = a[i];
            bucket[i] = a[i];
            sum[getId(i)] += a[i];
        }
        for (int i = 0; i < NUM_BLOCK; ++i)
        {
            int lef = getLef(i);
            int rig = getRig(i);
            for (int j = rig - 2; j >= lef; --j)
            {
                bucket[j] += bucket[j + 1];
            }
            sort(bucket.begin() + lef, bucket.begin() + rig);
            for (int j = rig - 2; j >= lef; --j)
            {
                minimize(bucket[j].second, bucket[j + 1].second);
            }
        }
    }
    inline int getId(int p)
    {
        return p / BLOCK_SIZE;
    }
    inline int getLef(int id)
    {
        return id * BLOCK_SIZE;
    }
    inline int getRig(int id)
    {
        return min(SIZE, (id + 1) * BLOCK_SIZE);
    }
    void update(int p, int x, int y)
    {
        int id = getId(p);
        sum[id] -= org[p];
        org[p] = make_pair(x, y);
        sum[id] += org[p];
        int lef = getLef(id);
        int rig = getRig(id);
        for (int i = lef; i < rig; ++i)
        {
            bucket[i] = org[i];
        }
        for (int i = rig - 2; i >= lef; --i)
        {
            bucket[i] += bucket[i + 1];
        }
        sort(bucket.begin() + lef, bucket.begin() + rig);
        for (int i = rig - 2; i >= lef; --i)
        {
            minimize(bucket[i].second, bucket[i + 1].second);
        }
    }
    long long getMin(int p)
    {
        int id = getId(p);
        long long res = LLONG_MAX;
        pair<long long, long long> cur = make_pair(0, 0);
        for (int i = p; i >= getLef(id); --i)
        {
            cur += org[i];
            if (cur.first >= S)
            {
                minimize(res, cur.second);
            }
        }
        for (int i = id - 1; i >= 0; --i)
        {
            int lef = getLef(i);
            int rig = getRig(i);
            auto it = lower_bound(bucket.begin() + lef, bucket.begin() + rig, make_pair(S - cur.first, LLONG_MIN));
            if (it != bucket.begin() + rig)
            {
                int pos = it - bucket.begin();
                minimize(res, bucket[pos].second + cur.second);
            }
            cur += sum[i];
        }
        return res == LLONG_MAX ? -1 : res;
    }
} group;

int main()
{
    cin.tie(nullptr)->sync_with_stdio(false);
#ifdef NHPHUCQT
    freopen("SEG.inp", "r", stdin);
    freopen("SEG.out", "w", stdout);
#endif

    cin >> n >> S;
    for (int i = 0; i < n; ++i)
    {
        cin >> a[i].first;
    }
    for (int j = 0; j < n; ++j)
    {
        cin >> a[j].second;
    }
    group.init();
    int q;
    cin >> q;
    while (q--)
    {
        int t;
        cin >> t;
        if (t == 1)
        {
            int i, x, y;
            cin >> i >> x >> y;
            group.update(i - 1, x, y);
        }
        else
        {
            int h;
            cin >> h;
            cout << group.getMin(h - 1) << '\n';
        }
    }
    return 0;
}

Bình luận

Mới nhất
Tải bình luận...

Không có bình luận nào.