---
# System prepended metadata

title: HSG lớp 9 TPHCM năm 2024 - 2025

---

# HSG lớp 9 TPHCM năm 2024 - 2025

[toc]

## Bài 1: Sắp xếp (7 điểm)

### Subtask 1:

Ở subtask này ta có $n \le 1000$, dùng thuật toán ==buble sort== như đề bài mô tả và đếm số thao tác đã thực hiện, làm như sau:

- Duyệt qua mọi cặp phần tử kề nhau $a_i$ và $a_{i+1}$, đổi chỗ 2 phần tử này cho nhau và tăng số lần hoán đổi lên nếu $a_i \gt a_{i+1}$.
- Lặp lại quá trình trên đến khi không còn thực hiện được nữa.


**Độ phức tạp thời gian:** $O \left( n^2 \right)$.

### subtask 2:

>[!Note]Nhận xét:
>Chắc chắn tồn tại thao tác đổi chỗ hai phần tử $a_i$ và $a_j (i \lt j)$ nếu $a_i \gt a_j$.
>Đáp án bài toán bây giờ chuyển thành đếm số cặp $i, j$ thỏa mãn $i < j$ và $a_i \gt a_j$.

>[!Tip]thuật toán:
>- Sử dụng cấu trúc dữ liệu ==segment tree== hoặc ==fenwick tree==.

**Cách làm:**
Xử lí bài toán offline, mỗi phần tử lưu dưới dạng $pair$ (lưu giá trị và chỉ số), $sort$ các phần tử lại theo thứ tự tăng dần của giá trị, nếu giá trị bằng nhau thì $sort$ tăng dần theo chỉ số.

$\rightarrow$ như vậy, nếu duyệt các phần tử theo thứ tự từ $1$ đến $n$, muốn biết được phần tử $a_i$ lớn hơn bao nhiêu phần tử $a_j (i \le j)$, đáp án chính là số lượng phần tử có chỉ số lớn hơn chỉ số của $a_i$ mà đã duyệt qua trước đó (vì chắc chắn giá trị của $a_i$ là lớn nhất cho đến hiện tại).
$\rightarrow$ ta có thể lưu và lấy giá trị đó bằng 1 trong 2 cấu trúc dữ liệu nói trên.

**Độ phức tạp thời gian:** $O \left( n \log  \right)$.

:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second

using namespace std;

const int Mod = 1e9 + 7;
const int   N = 2e5 + 5;

int n;
pii a[N];
int t[N];

void up(int x){
    while (x){
        t[x]++;
        x -= x & -x;
    }
}
int get(int x){
    int res = 0;
    while (x <= n){
        res += t[x];
        x += x & -x;
    }
    return res;
}

main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
        freopen("SAPXEP.inp","r",stdin);
        freopen("SAPXEP.out","w",stdout);

    cin >> n;
    for (int i = 1; i <= n; i++){
        cin >> a[i].fi; a[i].se = i;
    }
    sort(a+1, a+1+n);

    long long res = 0;
    for (int i = 1; i <= n; i++){
        res += get(a[i].se + 1);
        up(a[i].se);
    }
    cout << res;

    return 0;
}

```
:::

## Bài 2: Khu vực (6.5 điểm)

### subtask 1 và subtask 2:

>[!Note]Nhận xét:
>- được phép đổi 1 thẻ thành số bất kì, điều đó có nghĩa là số cần tìm phải là ước của ít nhất $n-1$ số trong dãy.
>- Hay nói cách khác, đáp án là ==ước chung lớn nhất== của $n-1$ phần tử bất kì trong dãy.

>[!Tip] Tính chất
> $gcd(a, b, c) = gcd(gcd(a, b), c)$

Thứ cần tính bây giờ là $gcd$ của $n-1$ phần tử bất kì, nghĩa là sẽ có 1 phần tử không cần xét. Ta có thể duyệt qua mọi phần tử không cần xét đó, rồi lấy $gcd$ những phần tử còn lại.

**Độ phức tạp thời gian** $O (n \log)$
>trong đó $\log$ là độ phức tạp của $\gcd$.

### subtask 3:

>[!Tip]Cải tiến:
>Sử dụng tư tưởng preffix và suffix, gọi $pre_i$ là $gcd(a_1, a_2, \cdots, a_i)$ và $suf_i$ là $gcd(a_i, a_{i+1}, \cdots, a_{n-1}, a_n)$.

$\rightarrow$ nếu chọn không tính phần tử thứ $i$ thì gcd của $n-1$ phần tử còn lại là $gcd(pre_{i-1}, suf{i+1})$.

**Độ phức tạp thời gian:** $O(n \log)$

:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()

using namespace std;

const int Mod = 1e9 + 7;
const int   N = 1e5 + 5;

int n, a[N], pre[N], suf[N];

main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
        freopen("KHUVUC.inp","r",stdin);
        freopen("KHUVUC.out","w",stdout);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];

    for (int i = 1; i <= n; i++) pre[i] = __gcd(pre[i-1], a[i]);
    for (int i = n; i >= 1; i--) suf[i] = __gcd(suf[i+1], a[i]);

    int res = 0;
    for (int i = 1; i <= n; i++)
        res = max(res, __gcd(pre[i-1], suf[i+1]));

    cout << res;

    return 0;
}
```
:::

## Bài 3: Giải đấu (6.5 điểm)

### subtask 1:

với mỗi truy vấn $l, r$ ta duyệt qua các vị trí $i$ $(l \le i \le r)$, chia các phần tử từ $l$ đến $i$ cho đội thứ nhất, các phần tử từ $i+1$ đến $r$ cho đội thứ 2.
Tính tổng ranking mỗi đội và lấy chệnh lệch nhỏ nhất.

**Độ phức tạp thời gian:** $O(q \cdot n^2)$

### subtask 2:

thuật toán giống như subtask 1, cải tiến phần lấy tổng mỗi đội bằng ==preffix sum==.

**Độ phức tạp thời gian:** $O(q \cdot n)$

### subtask 3:

>[!Note]Nhận xét:
>Gọi $sum$ là tổng ranking các phần tử từ $l$ đến $r$.
>Chênh lệch tổng của 2 đội là nhỏ nhất chỉ khi tổng của mỗi đội gần với $sum / 2$ nhất.

Dùng ==tìm kiếm nhị phân== để tìm ra vị trí $pos$ tối ưu mà tại đó, ta chia các phần tử từ $l$ đến $pos$ cho đội thứ nhất và các phần từ tử $pos+1$ đến $r$ cho đội thứ hai.

**Độ phức tạp thời gian:** $O(q \cdot \log n)$

:::success
:::spoiler Code
```cpp = 1
#include <bits/stdc++.h>
#define int long long

using namespace std;

const int Mod = 1e9 + 7;
const int   N = 1e6 + 5;

int n, q, sum[N];

int getPos(int i, int l, int r, int sumRange){
    int aim = sumRange / 2, pos;
    while (l <= r){
        int m = l+r >> 1;
        if (sum[m] - sum[i-1] >= aim) pos = m, r = m-1;
        else l = m+1;
    }
    return pos;
}

main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
        freopen("GIAIDAU.inp","r",stdin);
        freopen("GIAIDAU.out","w",stdout);

    cin >> n >> q;
    for (int i = 1; i <= n; i++){
        int x; cin >> x;
        sum[i] = sum[i-1] + x;
    }

    while (q--){
        int l, r; cin >> l >> r;
        int sumRange = sum[r] - sum[l-1];
        int pos = getPos(l, l, r, sumRange);

        int sumL = sum[pos] - sum[l-1], sumR = sum[r] - sum[pos];
        int res = abs(sumL - sumR);
        pos--;
        sumL = sum[pos] - sum[l-1], sumR = sum[r] - sum[pos];
        res = min(res, abs(sumL - sumR));

        cout << res << '\n';
    }

    return 0;
}

```
:::
