Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học
Thi thử miễn phí
🏆 Thi thử HSG Tin học tuần này trên ClueOJ
Mở công khai cho mọi người · đề do thầy Nguyễn Việt Nam Sơn biên soạn
Lịch thi, đề cũ & lời giải →

Bài 10: Struct, pair và sắp xếp theo nhiều tiêu chí trong C++ và Python (hàm so sánh cmp, xếp hạng)

Bài 10 – Struct, pair, sắp xếp nhiều tiêu chí
Series: Nhập môn lập trình thuật toán Độ khó: Dễ Bài 10 / Kiến thức nền tảng Struct và sắp xếp
// Nhập môn lập trình · Bài 10
Struct, pair và sắp xếp theo nhiều tiêu chí: bảng điểm, xếp hạng, hàm so sánh cmp
Một học sinh có họ tên, điểm, tuổi… Gom tất cả vào một biến duy nhất, rồi sắp xếp cả danh sách theo đúng luật mình muốn chỉ bằng một dòng sort.

Lời mở đầu

Ở bài 6 ta đã sắp xếp được một dãy số. Nhưng đề thi hiếm khi chỉ có số trần trụi: “Cho danh sách $n$ học sinh gồm họ tên và điểm, hãy in bảng xếp hạng theo điểm giảm dần, ai bằng điểm thì xếp theo tên”. Nếu dùng hai mảng riêng ten[] và diem[], mỗi lần đổi chỗ điểm lại phải nhớ đổi chỗ cả tên, rất dễ sai. Bài này giới thiệu struct (kiểu dữ liệu tự định nghĩa) và pair để gom nhiều thông tin thành một, rồi dùng sort kèm hàm so sánh để sắp xếp theo bất kỳ tiêu chí nào. Đây là dạng bài xuất hiện cực nhiều trong đề học sinh giỏi.

Trong bài này bạn sẽ học: khai báo và dùng struct, mảng struct; pair và cách hai pair được so sánh; hàm sort có sẵn và hàm so sánh cmp để sắp xếp theo nhiều tiêu chí (C++ và key= trong Python); sắp xếp kèm chỉ số ban đầu; xếp hạng có đồng hạng. Có khung chạy tay từng phép so sánh, mô phỏng “bảng xếp hạng” tự chọn tiêu chí và 8 bài tập kèm gợi ý.

Lộ trình bài viết, bấm vào từng phần để nhảy tới:

Phần 1Struct: tự tạo kiểu dữ liệu của riêng mình

struct (cấu trúc) cho phép em định nghĩa một kiểu dữ liệu mới gồm nhiều trường (thành phần). Ví dụ kiểu HocSinh gồm họ tên, điểm và tuổi. Sau khi định nghĩa, HocSinh dùng giống hệt int hay string: khai báo biến, khai báo mảng, truyền vào hàm… Truy cập từng trường bằng dấu chấm: hs.ten, hs.diem.

struct HocSinh string ten double diem int tuoi Mảng HocSinh a[3]: mỗi ô là trọn vẹn một học sinh "An"8.515 "Binh"9.014 "Chi"7.515 a[0]a[1]a[2] a[1].ten = "Binh", a[1].diem = 9.0. Đổi chỗ a[0] với a[1] là đổi trọn cả 3 trường.

Struct gom các thông tin của một đối tượng thành một khối. Mảng struct là dãy các khối đó.

Code C++ và Python
#include <iostream>
#include <string>
using namespace std;

struct HocSinh {          // định nghĩa kiểu mới, nhớ dấu ; ở cuối
    string ten;
    double diem;
    int tuoi;
};

int main(){
    HocSinh hs;                       // khai báo 1 biến kiểu HocSinh
    hs.ten = "An"; hs.diem = 8.5; hs.tuoi = 15;
    HocSinh b = {"Binh", 9.0, 14};    // gán nhanh theo đúng thứ tự các trường
    cout << b.ten << " " << b.diem << endl;
    HocSinh a[100];                   // mảng 100 học sinh
    a[0] = hs;
    cout << a[0].ten;
    return 0;
}
from dataclasses import dataclass

@dataclass
class HocSinh:            # Python dùng class (hoặc đơn giản là tuple)
    ten: str
    diem: float
    tuoi: int

hs = HocSinh("An", 8.5, 15)
b = HocSinh("Binh", 9.0, 14)
print(b.ten, b.diem)
a = [hs, b]               # list các học sinh
print(a[0].ten)
# Cách gọn hơn hay dùng khi thi: tuple ("An", 8.5, 15), truy cập bằng chỉ số t[0], t[1]

Ví dụ: nhập $n$ học sinh, mỗi học sinh gồm tên (một từ) và điểm. In ra học sinh có điểm cao nhất (nếu nhiều bạn bằng nhau, in bạn nhập trước). Nhờ struct, ta chỉ cần nhớ vị trí của học sinh giỏi nhất là có đủ cả tên lẫn điểm:

Code C++ và Python
#include <iostream>
#include <string>
using namespace std;

struct HocSinh {
    string ten;
    int diem;
};
HocSinh a[1005];

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)
        cin >> a[i].ten >> a[i].diem;
    int vt = 0;
    for(int i = 1; i < n; i++){
        if(a[i].diem > a[vt].diem)
            vt = i;
    }
    cout << a[vt].ten << " " << a[vt].diem;
    return 0;
}
n = int(input())
a = []
for _ in range(n):
    ten, diem = input().split()
    a.append((ten, int(diem)))
vt = 0
for i in range(1, n):
    if a[i][1] > a[vt][1]:
        vt = i
print(a[vt][0], a[vt][1])
C++▶ Chạy tay từng dòng codeTìm học sinh điểm cao nhất trong mảng struct: chỉ cần nhớ vị trí vt, từ đó lấy được cả tên lẫn điểm.
Dữ liệu mẫu:
1#include <iostream>
2#include <string>
3using namespace std;
4
5struct HocSinh {
6 string ten;
7 int diem;
8};
9HocSinh a[1005];
10
11int main(){
12 int n;
13 cin >> n;
14 for(int i = 0; i < n; i++)
15 cin >> a[i].ten >> a[i].diem;
16 int vt = 0;
17 for(int i = 1; i < n; i++){
18 if(a[i].diem > a[vt].diem)
19 vt = i;
20 }
21 cout << a[vt].ten << " " << a[vt].diem;
22 return 0;
23}

Phần 2pair: struct có sẵn gồm hai thành phần

Rất nhiều khi ta chỉ cần gom hai giá trị: tọa độ $(x, y)$, (điểm, chỉ số), (giá trị, số lần xuất hiện)… C++ có sẵn kiểu pair<kiểu1, kiểu2> trong thư viện <utility> (đã có sẵn trong <bits/stdc++.h>), hai thành phần tên là .first và .second. Điều đặc biệt nhất: hai pair so sánh được với nhau theo kiểu “từ điển”: so first trước, nếu bằng nhau mới so second. Vì vậy sort một mảng pair sẽ tự sắp theo first tăng dần, bằng thì theo second tăng dần, không cần viết gì thêm.

(3, 9) < (5, 1)đúng: 3 < 5, không cần nhìn second (4, 7) < (4, 2)sai: first bằng nhau, so second 7 > 2 (2, 2) == (2, 2)bằng nhau khi cả hai thành phần bằng

Ví dụ: sắp xếp $n$ điểm trên mặt phẳng theo $x$ tăng dần, cùng $x$ thì theo $y$ tăng dần. Để thấy rõ từng phép so sánh pair, khung chạy tay dùng sắp xếp chèn: lấy lần lượt từng phần tử, dịch các phần tử lớn hơn nó sang phải (giống thao tác chèn ở bài 6) rồi đặt nó vào chỗ trống. Trong thực tế em chỉ cần viết sort(a, a + n); là xong:

Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

pair<int, int> a[1005];

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)
        cin >> a[i].first >> a[i].second;
    // sort(a, a + n); cho kết quả y hệt đoạn dưới
    for(int i = 1; i < n; i++){
        pair<int, int> x = a[i];
        int j = i - 1;
        while(j >= 0 && a[j] > x){
            a[j + 1] = a[j];
            j--;
        }
        a[j + 1] = x;
    }
    for(int i = 0; i < n; i++)
        cout << a[i].first << " " << a[i].second << "\n";
    return 0;
}
n = int(input())
a = [tuple(map(int, input().split())) for _ in range(n)]
a.sort()          # tuple cũng so sánh theo kiểu từ điển như pair
for x, y in a:
    print(x, y)
C++▶ Chạy tay từng dòng codeSắp xếp chèn trên mảng pair: mỗi phép so sánh a[j] > x xét first trước, bằng nhau mới xét second.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4pair<int, int> a[1005];
5
6int main(){
7 int n;
8 cin >> n;
9 for(int i = 0; i < n; i++)
10 cin >> a[i].first >> a[i].second;
11 // sort(a, a + n); cho kết quả y hệt đoạn dưới
12 for(int i = 1; i < n; i++){
13 pair<int, int> x = a[i];
14 int j = i - 1;
15 while(j >= 0 && a[j] > x){
16 a[j + 1] = a[j];
17 j--;
18 }
19 a[j + 1] = x;
20 }
21 for(int i = 0; i < n; i++)
22 cout << a[i].first << " " << a[i].second << "\n";
23 return 0;
24}

Mô phỏng trực quan. So sánh hai pair thì so first trước, first bằng nhau mới so second; đọc thì hiểu nhưng tự làm tay lại hay rối. Em bấm Tiếp để xem sắp xếp chèn chạy trên mảng pair, mỗi lần so sánh đều ghi rõ máy đã nhìn vào first hay second.

MÔ PHỎNGSắp xếp chèn trên mảng pairNhập 2 đến 8 pair, mỗi pair là "first second", các pair cách nhau dấu phẩy.
x đang cần chènđang so sánh / vừa dờilớn hơn x, phải dờix vừa được đặtchưa xét

Phần 3Hàm sort và hàm so sánh cmp

Hàm sort(a, a + n) trong <algorithm> sắp xếp tăng dần với độ phức tạp $O(n \log n)$, nhanh hơn hẳn sắp xếp chọn hay nổi bọt $O(n^2)$ ở bài 6: $n = 10^6$ chỉ mất khoảng 0,1 giây. Với vector viết sort(v.begin(), v.end()). Muốn sắp xếp giảm dần hoặc theo luật riêng, ta truyền thêm tham số thứ ba là một hàm so sánh:

bool cmp(A x, A y) trả về true nếu và chỉ nếu x phải đứng TRƯỚC y trong kết quả

Ví dụ bảng điểm: điểm cao đứng trước, bằng điểm thì tên đứng trước theo thứ tự từ điển. Python không dùng hàm cmp mà dùng key: một hàm biến mỗi phần tử thành “khóa”, Python sắp xếp tăng dần theo khóa. Muốn điểm giảm dần thì lấy khóa là -diem.

Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

struct HS { string ten; int diem; };
HS a[1005];

bool cmp(HS x, HS y){
    if(x.diem != y.diem)
        return x.diem > y.diem;
    return x.ten < y.ten;
}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i].ten >> a[i].diem;
    // sort(a, a + n, cmp); cho kết quả y hệt đoạn dưới
    for(int i = 1; i < n; i++){
        HS x = a[i];
        int j = i - 1;
        while(j >= 0 && cmp(x, a[j])){
            a[j + 1] = a[j];
            j--;
        }
        a[j + 1] = x;
    }
    for(int i = 0; i < n; i++)
        cout << a[i].ten << " " << a[i].diem << "\n";
    return 0;
}
n = int(input())
a = []
for _ in range(n):
    ten, diem = input().split()
    a.append((ten, int(diem)))
a.sort(key=lambda h: (-h[1], h[0]))   # điểm giảm dần (dấu trừ), rồi tên tăng dần
for ten, diem in a:
    print(ten, diem)
C++▶ Chạy tay từng dòng codeMỗi lần hỏi cmp(x, a[j]): x có phải đứng trước a[j] không? Điểm khác nhau thì điểm cao đứng trước, bằng điểm thì so tên.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4struct HS { string ten; int diem; };
5HS a[1005];
6
7bool cmp(HS x, HS y){
8 if(x.diem != y.diem)
9 return x.diem > y.diem;
10 return x.ten < y.ten;
11}
12
13int main(){
14 int n;
15 cin >> n;
16 for(int i = 0; i < n; i++) cin >> a[i].ten >> a[i].diem;
17 // sort(a, a + n, cmp); cho kết quả y hệt đoạn dưới
18 for(int i = 1; i < n; i++){
19 HS x = a[i];
20 int j = i - 1;
21 while(j >= 0 && cmp(x, a[j])){
22 a[j + 1] = a[j];
23 j--;
24 }
25 a[j + 1] = x;
26 }
27 for(int i = 0; i < n; i++)
28 cout << a[i].ten << " " << a[i].diem << "\n";
29 return 0;
30}
Hàm cmp phải “nghiêm ngặt”: dùng >, <, không dùng >=, <=
Nếu viết return x.diem >= y.diem; thì với hai bạn bằng điểm, cmp(x, y) và cmp(y, x) đều trả về true: “x đứng trước y” và “y đứng trước x” cùng lúc, vô lý! Hàm sort của C++ có thể chạy sai, thậm chí truy cập ngoài mảng và bị RTE. Quy tắc: cmp(x, x) luôn phải trả về false. Ngoài ra nên truyền tham số dạng const HS &x để khỏi sao chép struct mỗi lần so sánh, chạy nhanh hơn.
Các mẫu sắp xếp hay dùng
sort(a, a + n, greater<int>()); sắp xếp giảm dần. sort(v.begin(), v.end()); sắp xếp vector. stable_sort giữ nguyên thứ tự ban đầu của các phần tử “bằng nhau”. Python: a.sort(reverse=True) giảm dần; sorted(a, key=len) theo độ dài; key=lambda t: (t[1], -t[0]) theo nhiều tiêu chí (mỗi tiêu chí giảm dần thì thêm dấu trừ nếu là số).

Thử ngay: mô phỏng dưới đây cho em tự nhập danh sách và chọn tối đa 3 tiêu chí theo thứ tự ưu tiên, máy sẽ sắp xếp chèn từng bước và giải thích mỗi phép so sánh dựa trên tiêu chí nào.

MÔ PHỎNGBảng xếp hạng nhiều tiêu chíMỗi dòng một học sinh: Tên Điểm Tuổi. Chọn thứ tự ưu tiên các tiêu chí rồi bấm Tiếp ▶ hoặc Tự chạy.
Đang tải mô phỏng…

Phần 4Hai ứng dụng: sắp xếp kèm chỉ số và xếp hạng có đồng hạng

Sắp xếp nhưng vẫn nhớ vị trí ban đầu. Nhiều đề hỏi “in ra chỉ số của các phần tử theo thứ tự tăng dần”. Sau khi sắp xếp, các số đã bị xáo trộn nên ta mất dấu chúng ở đâu. Mẹo: lưu mỗi phần tử dưới dạng pair(giá trị, chỉ số) rồi sắp xếp mảng pair. Nhờ cách so sánh pair, các giá trị bằng nhau tự xếp theo chỉ số tăng dần.

Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

pair<int, int> p[100005];

int main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> p[i].first;        // giá trị
        p[i].second = i;          // chỉ số ban đầu (đánh số từ 1)
    }
    sort(p + 1, p + n + 1);
    for(int i = 1; i <= n; i++) cout << p[i].second << " ";
    return 0;
}
n = int(input())
a = list(map(int, input().split()))
idx = sorted(range(1, n + 1), key=lambda i: (a[i - 1], i))
print(*idx)

Xếp hạng có đồng hạng. Trong thi đấu, các bạn bằng điểm nhau thì đồng hạng, và người tiếp theo bị “nhảy hạng”: điểm 10, 9, 9, 7 thì xếp hạng 1, 2, 2, 4. Sau khi sắp xếp giảm dần, hạng của người thứ $i$ (đếm từ 1) là: nếu bằng điểm người ngay trước thì cùng hạng người đó, ngược lại hạng chính là $i$.

Code C++ và Python
#include <bits/stdc++.h>
using namespace std;

int d[1005], hang[1005];

int main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> d[i];
    sort(d + 1, d + n + 1, greater<int>());
    for(int i = 1; i <= n; i++){
        if(i > 1 && d[i] == d[i - 1])
            hang[i] = hang[i - 1];
        else
            hang[i] = i;
    }
    for(int i = 1; i <= n; i++)
        cout << d[i] << " " << hang[i] << "\n";
    return 0;
}
n = int(input())
d = sorted(map(int, input().split()), reverse=True)
hang = [0] * n
for i in range(n):
    hang[i] = hang[i - 1] if i > 0 and d[i] == d[i - 1] else i + 1
for x, h in zip(d, hang):
    print(x, h)
C++▶ Chạy tay từng dòng codeXếp hạng có đồng hạng: bằng điểm người ngay trước thì cùng hạng, khác thì hạng bằng đúng thứ tự i.
Dữ liệu mẫu:
1#include <bits/stdc++.h>
2using namespace std;
3
4int d[1005], hang[1005];
5
6int main(){
7 int n;
8 cin >> n;
9 for(int i = 1; i <= n; i++) cin >> d[i];
10 sort(d + 1, d + n + 1, greater<int>());
11 for(int i = 1; i <= n; i++){
12 if(i > 1 && d[i] == d[i - 1])
13 hang[i] = hang[i - 1];
14 else
15 hang[i] = i;
16 }
17 for(int i = 1; i <= n; i++)
18 cout << d[i] << " " << hang[i] << "\n";
19 return 0;
20}

Mô phỏng trực quan. Xếp hạng có đồng hạng gồm ba bước nối tiếp nhau, dễ lạc ở bước "trả hạng về đúng người". Em nhập điểm của cả lớp rồi bấm Tiếp để xem từng bước; thử cho nhiều bạn bằng điểm để thấy hạng "nhảy cóc" như 1, 2, 2, 4.

MÔ PHỎNGXếp hạng có đồng hạng bằng sắp xếp kèm chỉ sốNhập điểm của 2 đến 10 học sinh (0 đến 100) theo thứ tự trong danh sách lớp.
đang xétngười ngay trước để sođã có hạng

Bài tậpLuyện struct, pair và sắp xếp

Giới hạn chung: $n \le 10^5$, nên mọi bài đều cần sắp xếp bằng sort $O(n \log n)$ thay vì tự viết sắp xếp $O(n^2)$. Tên là một từ, không có dấu cách.

Bài 1Thủ khoaDễ

Cho $n$ học sinh, mỗi dòng gồm tên và điểm (số nguyên). In tên học sinh có điểm cao nhất; nếu nhiều bạn bằng điểm, in bạn có tên nhỏ nhất theo thứ tự từ điển.

Input
4
Minh 9
An 9
Tuan 8
Bao 7
Output
An
💡 Gợi ý
Duyệt một lần như phần 1 nhưng điều kiện cập nhật là: điểm lớn hơn, hoặc bằng điểm và tên nhỏ hơn. Không cần sắp xếp cả mảng, độ phức tạp $O(n)$.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    string best = "";
    int bd = -1;
    for(int i = 0; i < n; i++){
        string t; int d;
        cin >> t >> d;
        if(d > bd || (d == bd && t < best)){
            bd = d; best = t;
        }
    }
    cout << best;
    return 0;
}
n = int(input())
a = [input().split() for _ in range(n)]
best = min(a, key=lambda t: (-int(t[1]), t[0]))
print(best[0])
Bài 2Sắp xếp điểm trên mặt phẳngDễ

Cho $n$ điểm $(x, y)$ nguyên. In các điểm theo $x$ tăng dần, cùng $x$ thì $y$ giảm dần.

Input
4
2 1
1 5
2 7
1 3
Output
1 5
1 3
2 7
2 1
💡 Gợi ý
Mảng pair sắp xếp mặc định theo $y$ tăng, nên cần hàm cmp: nếu x.first != y.first thì return x.first < y.first;, ngược lại return x.second > y.second;. Mẹo khác: lưu $(x, -y)$ rồi sort bình thường. Python: key=lambda p: (p[0], -p[1]).
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

pair<int, int> a[100005];

bool cmp(const pair<int, int> &p, const pair<int, int> &q){
    if(p.first != q.first) return p.first < q.first;
    return p.second > q.second;
}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i].first >> a[i].second;
    sort(a, a + n, cmp);
    for(int i = 0; i < n; i++) cout << a[i].first << " " << a[i].second << "\n";
    return 0;
}
n = int(input())
a = [tuple(map(int, input().split())) for _ in range(n)]
a.sort(key=lambda p: (p[0], -p[1]))
for x, y in a:
    print(x, y)
Bài 3Bảng xếp hạngDễ

Cho $n$ học sinh gồm tên và điểm. In tên theo điểm giảm dần, bằng điểm thì tên tăng dần theo thứ tự từ điển, mỗi tên một dòng.

Input
5
Nam 8
An 9
Lan 8
Bao 9
Hoa 7
Output
An
Bao
Lan
Nam
Hoa
💡 Gợi ý
Đúng hàm cmp ở phần 3. Struct gồm string ten; int diem;, gọi sort(a, a + n, cmp).
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

struct HS { string ten; int diem; };
HS a[100005];

bool cmp(const HS &x, const HS &y){
    if(x.diem != y.diem) return x.diem > y.diem;
    return x.ten < y.ten;
}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i].ten >> a[i].diem;
    sort(a, a + n, cmp);
    for(int i = 0; i < n; i++) cout << a[i].ten << "\n";
    return 0;
}
n = int(input())
a = []
for _ in range(n):
    t, d = input().split()
    a.append((t, int(d)))
a.sort(key=lambda h: (-h[1], h[0]))
print('\n'.join(t for t, d in a))
Bài 4Hạng của từng bạnTrung bình

Cho điểm của $n$ học sinh theo thứ tự nhập. In ra hạng của từng bạn theo đúng thứ tự nhập ban đầu (điểm cao hạng nhỏ, bằng điểm đồng hạng, người sau bị nhảy hạng).

Input
5
8 9 8 7 9
Output
3 1 3 5 1
💡 Gợi ý
Kết hợp cả hai ứng dụng ở phần 4: lưu pair(điểm, chỉ số), sắp xếp giảm dần theo điểm, tính hạng như khung chạy tay rồi ghi vào kq[chỉ số ban đầu]. Cuối cùng in mảng kq theo thứ tự 1..n.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

pair<int, int> p[100005];
int kq[100005];

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){ cin >> p[i].first; p[i].second = i; }
    sort(p, p + n, [](const pair<int,int> &a, const pair<int,int> &b){ return a.first > b.first; });
    int hang = 0;
    for(int i = 0; i < n; i++){
        if(i == 0 || p[i].first != p[i - 1].first) hang = i + 1;
        kq[p[i].second] = hang;
    }
    for(int i = 0; i < n; i++) cout << kq[i] << " ";
    return 0;
}
n = int(input())
a = list(map(int, input().split()))
s = sorted(a, reverse=True)
hang = {}
for i, x in enumerate(s):
    if x not in hang:
        hang[x] = i + 1
print(*[hang[x] for x in a])
Bài 5Thứ tự chỉ sốDễ

Cho dãy $n$ số. In ra các chỉ số (đánh số từ 1) theo thứ tự giá trị tăng dần; giá trị bằng nhau thì chỉ số nhỏ in trước.

Input
5
30 10 20 10 50
Output
2 4 3 1 5
💡 Gợi ý
Đúng mẹo pair(giá trị, chỉ số) ở phần 4, sort mặc định là đủ vì pair tự so second khi first bằng nhau.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

pair<int, int> p[100005];

int main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){ cin >> p[i].first; p[i].second = i; }
    sort(p + 1, p + n + 1);
    for(int i = 1; i <= n; i++) cout << p[i].second << " ";
    return 0;
}
n = int(input())
a = list(map(int, input().split()))
print(*sorted(range(1, n + 1), key=lambda i: (a[i - 1], i)))
Bài 6Sắp xếp theo tổng chữ sốTrung bình

Cho $n$ số nguyên dương ($\le 10^9$). Sắp xếp theo tổng chữ số tăng dần; cùng tổng chữ số thì số nhỏ hơn đứng trước.

Input
5
91 19 100 23 5
Output
100 5 23 19 91
💡 Gợi ý
Tính trước tổng chữ số của từng số, lưu pair(tổng chữ số, số) rồi sort. Đừng tính tổng chữ số bên trong hàm cmp: cmp được gọi khoảng $n \log n$ lần, tính đi tính lại sẽ chậm.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

pair<int, int> p[100005];

int tcs(int x){
    int s = 0;
    while(x > 0){ s += x % 10; x /= 10; }
    return s;
}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){
        int x; cin >> x;
        p[i] = {tcs(x), x};
    }
    sort(p, p + n);
    for(int i = 0; i < n; i++) cout << p[i].second << " ";
    return 0;
}
n = int(input())
a = list(map(int, input().split()))
a.sort(key=lambda x: (sum(map(int, str(x))), x))
print(*a)
Bài 7Chọn nhiều hoạt động nhấtKhá

Có $n$ hoạt động, hoạt động thứ $i$ bắt đầu lúc $s_i$ và kết thúc lúc $e_i$. Một người chỉ tham gia được một hoạt động tại mỗi thời điểm (hoạt động sau được bắt đầu đúng lúc hoạt động trước kết thúc). Hỏi tham gia được nhiều nhất bao nhiêu hoạt động?

Input
5
1 3
2 5
4 6
6 8
5 7
Output
3
💡 Gợi ý
Bài toán kinh điển của thuật toán tham lam: sắp xếp các hoạt động theo thời điểm kết thúc tăng dần, rồi duyệt lần lượt, hoạt động nào bắt đầu không sớm hơn lúc kết thúc của hoạt động vừa chọn thì chọn nó. Chọn hoạt động kết thúc sớm nhất luôn để lại nhiều thời gian nhất cho các hoạt động sau.
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

pair<int, int> a[100005];   // lưu (kết thúc, bắt đầu) để sort theo kết thúc

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){
        int s, e; cin >> s >> e;
        a[i] = {e, s};
    }
    sort(a, a + n);
    int dem = 0, ketthuc = INT_MIN;
    for(int i = 0; i < n; i++){
        if(a[i].second >= ketthuc){
            dem++;
            ketthuc = a[i].first;
        }
    }
    cout << dem;
    return 0;
}
n = int(input())
a = [tuple(map(int, input().split())) for _ in range(n)]
a.sort(key=lambda t: t[1])
dem, kt = 0, float('-inf')
for s, e in a:
    if s >= kt:
        dem += 1
        kt = e
print(dem)
Bài 8Sắp xếp từDễ

Cho $n$ từ. Sắp xếp theo độ dài tăng dần, cùng độ dài thì theo thứ tự từ điển. In mỗi từ một dòng.

Input
5
banana
apple
kiwi
fig
cherry
Output
fig
kiwi
apple
banana
cherry
💡 Gợi ý
Hàm cmp: if(x.size() != y.size()) return x.size() < y.size(); return x < y;. Python: key=lambda w: (len(w), w).
Xem code tham khảo (C++ / Python)
#include <bits/stdc++.h>
using namespace std;

string a[100005];

bool cmp(const string &x, const string &y){
    if(x.size() != y.size()) return x.size() < y.size();
    return x < y;
}

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];
    sort(a, a + n, cmp);
    for(int i = 0; i < n; i++) cout << a[i] << "\n";
    return 0;
}
n = int(input())
a = [input().strip() for _ in range(n)]
a.sort(key=lambda w: (len(w), w))
print('\n'.join(a))
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇
Bản quyền thuộc về Thầy Nguyễn Việt Nam Sơn. Bài viết, lời giải, hình minh hoạ và mã nguồn trên trang là công sức biên soạn của thầy qua nhiều năm giảng dạy. Vui lòng không sao chép, đăng lại hay sử dụng dưới mọi hình thức khi chưa được sự đồng ý của tác giả. Nếu thấy hữu ích, hãy chia sẻ bằng đường link bài gốc nhé. Cảm ơn bạn!
Tự học thuật toán: khó ở đâu?
Đọc xong một bài giải là bước đầu. Phần lớn học sinh tự học sẽ gặp những trở ngại sau:
  • Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
  • Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
  • Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
  • Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
  • Dễ nản giữa chừng khi không có ai theo sát.
Học cùng thầy Nam Sơn, các trở ngại đó được gỡ bỏ:
  • Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
  • Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
  • Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
  • Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.

Nhận xét