Hướng dẫn giải của Tìm số nhỏ thứ K trong mảng


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.

Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.

Bạn phải đăng nhập để mở lời giải này.

Đăng nhập

Tác giả: Hiếu Nguyễn, kjfvnju, CaoTrung

Editorial for aquery2: Tìm số nhỏ thứ K trong mảng

Hiểu bài toán

Bài toán yêu cầu tìm số nhỏ thứ K trong một dãy số được tạo ra bằng cách thực hiện Q thao tác. Mỗi thao tác chèn bi số có giá trị ai vào cuối dãy. Dãy số này có thể rất lớn (tổng b_i lên tới 10^14), nên không thể lưu trữ trực tiếp. Ta chỉ cần tìm giá trị của phần tử thứ K khi sắp xếp dãy số tăng dần.

Các cách tiếp cận

Cách Brute Force Simulation
#include <stdio.h>
#include <stdlib.h>

int cmpf(const void *a, const void *b) {
    int int_a = *(int*)a;
    int int_b = *(int*)b;
    if (int_a < int_b) return -1;
    if (int_a > int_b) return 1;
    return 0;
}

int main() {
    // Kích thước mảng cố định, dễ bị tràn bộ nhớ hoặc sai giới hạn
    int arr[1000001];
    int temp1, temp2;
    int pos;
    int n, i, j, cur = 0;
    scanf("%d", &n);
    for(i = 0; i < n; i++){
        scanf("%d %d", &temp1, &temp2);
        for(j = 0; j < temp2; j++){
            arr[cur] = temp1;
            cur++;
        }
    }
    scanf("%d", &pos);
    qsort(arr, cur, sizeof(int), cmpf);
    printf("%d", arr[pos-1]);
    return 0;
}
  • Time Complexity: O(TotalElements * log TotalElements)
  • Space Complexity: O(Total_Elements)

Approach này tạo ra toàn bộ dãy số và sắp xếp nó. Điều này chỉ hiệu quả nếu tổng số lượng phần tử (tổng của các bi) nhỏ. Với ràng buộc của bài toán (tổng bi có thể lên tới 10^14), approach này sẽ tràn bộ nhớ và quá thời gian xử lý.

Cách Binary Search on Answer
#include <bits/stdc++.h>
using namespace std;

struct Op {
    long long a, b;
};

vector<Op> ops;
long long K;
int Q;

// Tính số lượng phần tử <= val trong dãy
long long count_le(long long val) {
    long long cnt = 0;
    for (const auto& op : ops) {
        if (op.a <= val) {
            cnt += op.b;
        }
    }
    return cnt;
}

int main() {
    cin >> Q;
    ops.resize(Q);
    long long total_b = 0;
    for (int i = 0; i < Q; i++) {
        cin >> ops[i].a >> ops[i].b;
        total_b += ops[i].b;
    }
    cin >> K;

    // Binary Search tìm giá trị x sao cho số lượng phần tử <= x >= K
    // Giá trị a_i nằm trong [1, 10^9]
    long long left = 1, right = 1e9 + 7;
    long long ans = right;

    while (left <= right) {
        long long mid = left + (right - left) / 2;
        if (count_le(mid) >= K) {
            ans = mid;
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }

    cout << ans << endl;
    return 0;
}
  • Time Complexity: O(Q * log(10^9))
  • Space Complexity: O(Q)

Ta sử dụng kỹ thuật Binary Search trên không gian giá trị (từ 1 đến 10^9). Với mỗi giá trị 'mid', ta đếm xem có bao nhiêu số trong dãy nhỏ hơn hoặc bằng 'mid'. Nếu số lượng này >= K, thì giá trị nhỏ thứ K phải nằm ở nửa bên trái (bao gồm mid). Ngược lại, nó nằm ở nửa phải. Hàm đếm chỉ cần duyệt qua Q thao tác, rất nhanh.

Cách Sorting and Counting (Optimal)
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int Q;
    cin >> Q;

    vector<pair<long long, long long>> ops(Q);
    for (int i = 0; i < Q; i++) {
        cin >> ops[i].first >> ops[i].second;
    }

    long long K;
    cin >> K;

    // Sắp xếp các cặp (a_i, b_i) theo giá trị a_i tăng dần
    sort(ops.begin(), ops.end());

    // Duyệt qua các thao tác đã sắp xếp
    for (int i = 0; i < Q; i++) {
        // Nếu K nhỏ hơn hoặc bằng số lượng phần tử trong thao tác hiện tại,
        // thì số nhỏ thứ K chính là giá trị a_i của thao tác này.
        if (K <= ops[i].second) {
            cout << ops[i].first << endl;
            return 0;
        }
        // Ngược lại, trừ đi số lượng phần tử của thao tác này và tiếp tục
        K -= ops[i].second;
    }

    return 0;
}
  • Time Complexity: O(Q log Q)
  • Space Complexity: O(Q)

Đây là approach tối ưu nhất. Thay vì binary search, ta nhận thấy rằng nếu sắp xếp các thao tác theo giá trị ai tăng dần, thì các số trong dãy cũng sẽ xuất hiện theo thứ tự giá trị tăng dần (từ các thao tác có ai nhỏ nhất). Ta chỉ cần duyệt qua danh sách đã sắp xếp và trừ dần K cho đến khi K nhỏ hơn hoặc bằng bi của thao tác hiện tại. Khi đó, ai chính là đáp án.

Phân tích độ phức tạp

Cách tiếp cận Time Space Tên
1 O(TotalElements * log TotalElements) O(Total_Elements) Brute Force Simulation
2 O(Q * log(10^9)) O(Q) Binary Search on Answer
3 O(Q log Q) O(Q) Sorting and Counting (Optimal)

Bài học kinh nghiệm

  • Bài toán không cần xây dựng toàn bộ mảng, chỉ cần tìm giá trị tại vị trí K.
  • Số lượng thao tác Q (10^5) nhỏ hơn nhiều so với tổng số phần tử (10^14), nên thuật toán cần dựa vào Q chứ không dựa vào tổng số phần tử.
  • Nếu sắp xếp các thao tác theo giá trị a_i, ta có thể mô phỏng quá trình sắp xếp toàn bộ mảng một cách gián tiếp.

Lỗi thường gặp

  • Sử dụng biến kiểu int để lưu tổng số phần tử hoặc K, gây tràn số (overflow) vì các giá trị này có thể lên tới 10^14.
  • Lập trình mô phỏng trực tiếp (chèn từng phần tử vào vector/mảng) gây quá thời gian hoặc tràn bộ nhớ.
  • Quên trường hợp K nằm trong chính khối lượng phần tử của thao tác đầu tiên sau khi sắp xếp.

Bình luận

Please read the guidelines before commenting.


Không có bình luận tại thời điểm này.