Nói Chuyện Riêng (ARGUMENT)
Đề bài
Bài 3: Nói chuyện riêng [ARGUMENT] – 2 điểm
Trong giờ tập quân sự, học sinh của tất cả các lớp xếp thành một hàng dài để hành quân dã ngoại. Để phân biệt lớp này với lớp khác, mỗi lớp được mô tả bằng một mã định danh riêng. Lớp có mã định danh càng lớn thì yêu cầu về tính kỷ luật khi hành quân càng cao.
Các bạn học sinh cùng một lớp thường nói chuyện ngay cả khi đang hành quân. Chính xác hơn, nếu có hai bạn cùng lớp có vị trí trong hàng khác nhau không quá $K$ thì họ sẽ nói chuyện với nhau.
Hãy xác định số hiệu lớn nhất của lớp có hai bạn nói chuyện với nhau (để còn kỷ luật mẫu!!!)
Input:
- Dòng đầu tiên ghi hai số nguyên dương $N$, $K$ lần lượt là số bạn học sinh trong hàng và khoảng cách tối đa để có thể nói chuyện với nhau $(1 \le N \le 50000,\ 1 \le K \le N)$.
- $N$ dòng sau, mỗi dòng ghi định danh lớp của một bạn đứng trong hàng hành quân (theo thứ tự từ $1$ đến $N$).
Output: Một số nguyên duy nhất là mã định danh lớn nhất của lớp có hai bạn học sinh nói chuyện với nhau. Nếu không có ai nói chuyện riêng thì ghi $-1$.
6 3
7
3
4
2
3
44Lời mở đầu
Bài này mình được một em học viên hỏi và mình đã hướng dẫn chi tiết cách thức giải quyết cho em ấy. Tin rằng sẽ có giá trị với nhiều bạn nên mình viết lại thành bài giải chi tiết để các bạn tham khảo nhen. Hi vọng mang đến được giá trị cho các bạn 😇.
Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì link bài đây nhé:
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJTrước tiên các bạn đảm bảo là hiểu rõ đề rồi hen. Ta có nhiều lớp học, mỗi lớp có 1 ID riêng, và có danh sách $N$ học sinh đứng thành hàng, biết từng học sinh thuộc lớp có ID bao nhiêu. Như ví dụ: đầu hàng là số $7$ nghĩa là bạn đứng đầu thuộc lớp ID $= 7$; tiếp đó là số $3$ nghĩa là bạn thứ 2 thuộc lớp ID $= 3$... 2 học sinh nếu cùng lớp và khoảng cách giữa họ (trị tuyệt đối hiệu 2 vị trí, ví dụ bạn thứ 2 với bạn thứ 5 thì khoảng cách là $3$) mà $\le K$ thì sẽ nói chuyện với nhau. Trong các cặp nói chuyện, chọn lớp có ID lớn nhất để in ra; không có cặp nào thì in $-1$.
- Bạn thứ 2 nói chuyện với bạn thứ 5: cùng lớp $3$, khoảng cách $5 - 2 = 3 \le K = 3$.
- Bạn thứ 3 nói chuyện với bạn thứ 6: cùng lớp $4$, khoảng cách $6 - 3 = 3 \le 3$.
- Bạn thứ 1 (lớp $7$) và bạn thứ 4 (lớp $2$) không nói chuyện với ai vì lớp của họ chỉ có mình họ.
Vậy có 2 cặp nói chuyện thuộc lớp $3$ và lớp $4$, ID lớn nhất là 4.
Lộ trình lời giải, bấm vào từng phần để nhảy tới:
Cách 1Bảng băm lưu vị trí xuất hiện gần nhất của mỗi lớp – O(N)
Bắt đầu xét từ bạn học sinh đầu tiên. Dùng 1 cái bảng băm (hash table), trong C++ là unordered_map. Nó giống như 1 cuốn từ điển: truyền vào key thì nó trả ra value. Ở đây key là ID lớp, value là vị trí của bạn học sinh lớp đó. Khai báo: unordered_map<int, int> ump;.
Xét bạn thứ 1: lưu key $= 7$, value $= 1$ (lớp 7 xuất hiện tại vị trí 1). Xét bạn thứ 2: lưu key $= 3$, value $= 2$... Cứ xét đến bạn thứ $i$ có ID lớp là $x$ thì tìm xem $x$ đã có trong bảng băm chưa: auto it = ump.find(x);
- Nếu không tìm thấy (
it == ump.end()): thêm mớiump[x] = i. - Nếu tìm thấy: nghĩa là trước đó (các vị trí $1..i-1$) đã từng có bạn cùng lớp $x$. Tính khoảng cách
i - it->second(it->firstlà key,it->secondlà value). Nếu $\le K$ thì 2 bạn này nói chuyện ⇒ cập nhậtMax = max(Max, x)(ban đầuMax = -1). Sau đó luôn cập nhậtit->second = i.
Minh hoạ
Em nhập $K$ và dãy ID lớp rồi bấm Tiếp. Ô vàng là bạn đang xét, ô tím là bạn cùng lớp gần nhất phía trước (lấy từ bảng băm); xanh lá là cặp nói chuyện được, đỏ là xa quá.
Source code để các bạn tham khảo nếu làm theo hướng này:
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int n, k;
cin >> n >> k;
int Max = -1;
unordered_map<int, int> ump;
for(int i = 1; i <= n; ++i){
int x;
cin >> x;
auto it = ump.find(x);
if(it == ump.end()){
ump[x] = i;
}
else{
if(i - it->second <= k){
Max = max(Max, x);
}
it->second = i;
}
}
cout << Max;
return 0;
}
import sys
def main():
data = sys.stdin.buffer.read().split()
n, k = int(data[0]), int(data[1])
Max = -1
ump = {}
for i in range(1, n + 1):
x = int(data[1 + i])
if x not in ump:
ump[x] = i
else:
if i - ump[x] <= k:
Max = max(Max, x)
ump[x] = i
print(Max)
main()
Đánh giá độ phức tạp của cách làm này:
- Không gian: $O(N)$ cho bảng băm (tối đa $N$ lớp khác nhau).
- Thời gian:
unordered_mapvới các thao tác insert, find trung bình $O(1)$ nên tổng quát chỉ $O(N)$. $N$ tối đa $5 \cdot 10^4$, nhỏ hơn rất nhiều so với ngưỡng $10^8$ của 1 giây ⇒ yên tâm.
unordered_map được. Lúc này bạn nào đọc không kỹ đề sẽ tưởng các ID lớp nằm trong đoạn $[1, N]$ rồi dùng mảng đánh dấu thay cho unordered_map. Nhưng điều này là sai: đề không hề nói rõ ID lớp nằm trong đoạn nào, nó có thể tới 1 tỷ, và tất nhiên không thể tạo mảng đánh dấu 1 tỷ phần tử được. Giải pháp là cách 2 bên dưới.Cách 2Sắp xếp các cặp (ID lớp, vị trí) – O(N log N)
Nếu nhìn kỹ vấn đề sẽ thấy bài này hoàn toàn chỉ cần mảng thông thường, với sự hỗ trợ của kỹ thuật sắp xếp. Với ví dụ mẫu, đánh số thứ tự học sinh từ $1$ đến $N$ thì ta có danh sách cặp (ID lớp, STT): $(7, 1), (3, 2), (4, 3), (2, 4), (3, 5), (4, 6)$.
Đi sắp xếp giảm dần theo ID lớp, nếu ID lớp trùng nhau thì sắp tăng dần theo STT:
$(7, 1),\ (4, 3),\ (4, 6),\ (3, 2),\ (3, 5),\ (2, 4)$
Đặc tính của sắp xếp đã giúp gom nhóm các ID lớp bằng nhau về nằm sát cạnh nhau. Giờ chỉ việc duyệt 1 vòng lặp: xét phần tử $i$ với lân cận trước nó $i - 1$, nếu trùng ID lớp thì lấy STT của $i$ trừ STT của $i - 1$ (STT lớn trừ STT nhỏ) xem có $\le K$ không. Nếu thỏa thì dừng luôn, in ra ID lớp đó – vì danh sách đang giảm dần theo ID nên đó chính là ID lớn nhất. Xét hết danh sách mà không thấy thì in $-1$. Ví dụ trên: $(4, 3)$ và $(4, 6)$ cùng lớp, $6 - 3 = 3 \le 3$ ⇒ in $4$ ngay.
Source code để các bạn tham khảo nếu làm theo hướng này:
#include <bits/stdc++.h>
using namespace std;
pair<int, int> a[50005];
bool cmp(const pair<int, int> &a, const pair<int, int> &b){
if(a.first == b.first){
return a.second < b.second;
}
return a.first > b.first;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int n, k;
cin >> n >> k;
for(int i = 1; i <= n; ++i){
cin >> a[i].first;
a[i].second = i;
}
sort(a + 1, a + n + 1, cmp);
for(int i = 2; i <= n; ++i){
if(a[i].first == a[i - 1].first && a[i].second - a[i - 1].second <= k){
cout << a[i].first;
return 0;
}
}
cout << -1;
return 0;
}
import sys
def main():
data = sys.stdin.buffer.read().split()
n, k = int(data[0]), int(data[1])
a = [(int(data[1 + i]), i) for i in range(1, n + 1)]
# giảm dần theo mã lớp, cùng mã lớp thì tăng dần theo vị trí
a.sort(key=lambda p: (-p[0], p[1]))
for i in range(1, n):
if a[i][0] == a[i - 1][0] and a[i][1] - a[i - 1][1] <= k:
print(a[i][0])
return
print(-1)
main()
Đánh giá độ phức tạp của cách làm này:
- Không gian: $O(N)$ cho mảng cặp.
- Thời gian: input $O(N)$, sắp xếp $O(N \log N)$, xử lý $O(N)$ ⇒ lấy theo thằng lớn nhất: $O(N \log N)$. $N = 50000$ thì $50000 \cdot \log_2 50000$ chưa đến 1 triệu, trong khi ngưỡng 1 giây tối đa là 100 triệu nên không sợ TLE.
unordered_map, còn cách 2 chỉ dùng mảng bình thường. Các cấu trúc dữ liệu có sẵn trong thư viện STL như vector, stack, queue, map, set, unordered_map... thường chạy chậm hơn cỡ 3 đến 5 lần so với cách giải tương tự chỉ dùng mảng thường. Nên một thuật $O(N \log N)$ với $N$ tới $10^6$ mà dùng STL thì vẫn có khả năng TLE vài test lớn. Muốn chắc ăn nhất thì dùng mảng thường; tuy nhiên không phải lúc nào cũng bắt buộc, có những tình huống dùng thư viện có sẵn lại tối ưu hơn, tùy tình huống mà áp dụng nhé.pair<int, int> a[50005] thay cho mảng kích thước biến a[n + 1] trong hàm main (vốn không thuộc chuẩn C++), logic giữ nguyên.- 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.
- 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
Đăng nhận xét