Khóa Số (HSG12 Hà Nội 2025 – Bài 1)
Đề bài
Bài I. Khoá số (5,0 điểm)
Hải dùng ổ khóa số (Hình ảnh) để khóa tủ cá nhân tại đội tuyển. Khóa gồm có bốn vòng số, mỗi vòng gồm $10$ chữ số từ $0$ đến $9$. Các vòng số của khóa này có thể xoay tròn theo chiều kim đồng hồ hoặc ngược lại.
Hải đặt mật khẩu khóa của mình theo thứ tự từ trên xuống dưới của vị trí chốt là $\{2, 0, 2, 5\}$. Mỗi lần khóa, Hải xoay các số đi, khi nào muốn mở thì lại đưa các số về đúng dãy $\{2, 0, 2, 5\}$. Mỗi lần xoay thì một chữ số sẽ chuyển thành số kề bên trái hoặc kề bên phải của nó.
Chú ý: kề bên trái của $0$ là $9$, kề bên phải của $9$ là $0$.
Yêu cầu: Cho biết $4$ chữ số $A, B, C, D$ lần lượt là các chữ số đang xuất hiện từ trên xuống dưới của vị trí chốt. Em hãy lập trình tính giúp Hải xem phải xoay ít nhất bao nhiêu lần để có thể mở khóa.
Dữ liệu vào từ file văn bản KHOASO.INP: Gồm bốn chữ số $A, B, C, D$ trên cùng một dòng, cách nhau bởi một dấu cách $(0 \le A, B, C, D \le 9)$.
Kết quả ghi ra file văn bản KHOASO.OUT: Một số nguyên duy nhất là kết quả của bài toán.
Lưu ý: trên hệ thống chấm online CLUE OJ thì đọc dữ liệu từ bàn phím và in kết quả ra màn hình (giới hạn trên CLUE OJ: 1 giây, 1 GB).
2 3 8 111- Chữ số thứ nhất giữ nguyên;
- Chữ số thứ hai xoay từ $3$ thành $0$ mất $3$ lần xoay;
- Chữ số thứ ba xoay từ $8$ thành $2$ mất $4$ lần xoay như sau: $8 \to 9 \to 0 \to 1 \to 2$;
- Chữ số thứ tư xoay từ $1$ thành $5$ mất $4$ lần xoay;
Lời mở đầu
Đây là bài 1 – bài "khởi động" của đề thi học sinh giỏi Tin học lớp 12 thành phố Hà Nội năm 2025. Bài dễ nhưng rất hay bị mất điểm oan vì quên trường hợp "vòng qua" $9 \to 0$ hoặc $0 \to 9$. Mình viết lời giải chi tiết để nhiều bạn được tham khảo luôn, nhất là các bạn đang ôn thi HSG. Hy 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 OJMình tóm tắt lại đề cho dễ hình dung: có $4$ vòng số, mỗi vòng đang hiện một chữ số. Mỗi lần xoay, ta chọn một vòng và đẩy chữ số của nó lên $1$ (sang phải: $3 \to 4$, …, $9 \to 0$) hoặc xuống $1$ (sang trái: $3 \to 2$, …, $0 \to 9$). Cần đưa $4$ vòng về đúng $2, 0, 2, 5$ với ít lần xoay nhất.
Vì mỗi lần xoay chỉ tác động lên đúng một vòng, nên các vòng hoàn toàn độc lập với nhau: số lần xoay ít nhất của cả ổ khóa = tổng số lần xoay ít nhất của từng vòng. Với ví dụ 2 3 8 1:
| Vòng | Đang là | Cần về | Xoay phải (tăng) | Xoay trái (giảm) | Ít nhất |
|---|---|---|---|---|---|
| 1 | 2 | 2 | 0 | 0 | 0 |
| 2 | 3 | 0 | 3→4→…→9→0: 7 | 3→2→1→0: 3 | 3 |
| 3 | 8 | 2 | 8→9→0→1→2: 4 | 8→7→…→2: 6 | 4 |
| 4 | 1 | 5 | 1→2→3→4→5: 4 | 1→0→9→8→7→6→5: 6 | 4 |
Tổng: $0 + 3 + 4 + 4 = 11$ – đúng với đáp án của đề.
Lộ trình lời giải, bấm vào từng phần để nhảy tới:
Cách 1Xoay thử theo từng chiều rồi lấy min – O(1)
Ta sẽ xử lý riêng đối với từng chốt (từng vòng số). Ở đây ta gọi:
- $right(x, y)$: số lần xoay để đưa chữ số $x$ thành $y$ mà chỉ sử dụng thao tác xoay phải (tăng lên $1$, sau $9$ là $0$).
- $left(x, y)$: số lần xoay để đưa chữ số $x$ thành $y$ mà chỉ sử dụng thao tác xoay trái (giảm đi $1$, trước $0$ là $9$).
Dễ thấy có thể tính những hàm này bằng cách duyệt trâu: cứ xoay từng nấc một và đếm, đến khi $x = y$ thì dừng. Mỗi vòng chỉ có $10$ chữ số nên đi một chiều tối đa $9$ nấc là tới – vòng lặp chạy rất ít.
Kết quả bài toán sẽ là:
Cái "vòng qua" $9 \to 0$ và $0 \to 9$ được xử lý gọn bằng phép chia lấy dư cho $10$:
- Xoay phải:
x++; x %= 10;– khi $x = 9$ thì $x + 1 = 10$, $10 \bmod 10 = 0$. Đúng "kề bên phải của 9 là 0". - Xoay trái:
x--; x += 10; x %= 10;– khi $x = 0$ thì $x - 1 = -1$, cộng $10$ được $9$, $9 \bmod 10 = 9$. Đúng "kề bên trái của 0 là 9". Còn với $x \ge 1$ thì cộng $10$ rồi chia dư $10$ không làm thay đổi gì.
% với số âm cho kết quả âm: -1 % 10 bằng -1 chứ không phải 9. Nếu chỉ viết x--; x %= 10; thì từ $0$ xoay trái sẽ ra $-1$, vòng while(x != y) chạy mãi không dừng! (Python thì -1 % 10 = 9, nhưng mình vẫn giữ + 10 cho giống code C++.)Minh hoạ xoay từng vòng số
Em nhập $4$ chữ số đang hiện trên khóa rồi bấm Tiếp: với mỗi vòng, khung dưới tô đường đi khi xoay phải (xanh lá) và khi xoay trái (cam), rồi chọn chiều ngắn hơn.
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 A, B, C, D;
int right(int x, int y){
int ans = 0;
while (x != y){
x ++;
x %= 10;
ans ++;
}
return ans;
}
int left(int x, int y){
int ans = 0;
while (x != y){
x --;
x += 10;
x %= 10;
ans ++;
}
return ans;
}
int main(){
if(fopen("KHOASO.INP", "r")){
freopen("KHOASO.INP", "r", stdin);
freopen("KHOASO.OUT", "w", stdout);
}
ios::sync_with_stdio(0);
cin.tie(0);
cin >> A >> B >> C >> D;
cout << min(right(A, 2), left(A, 2))
+ min(right(B, 0), left(B, 0))
+ min(right(C, 2), left(C, 2))
+ min(right(D, 5), left(D, 5)) << endl;
}
import sys, os
def right(x, y):
ans = 0
while x != y:
x += 1
x %= 10
ans += 1
return ans
def left(x, y):
ans = 0
while x != y:
x -= 1
x += 10
x %= 10
ans += 1
return ans
if os.path.exists("KHOASO.INP"):
sys.stdin = open("KHOASO.INP", "r")
sys.stdout = open("KHOASO.OUT", "w")
A, B, C, D = map(int, sys.stdin.read().split()[:4])
print(min(right(A, 2), left(A, 2))
+ min(right(B, 0), left(B, 0))
+ min(right(C, 2), left(C, 2))
+ min(right(D, 5), left(D, 5)))
Các bạn bấm Tiếp để chạy tay từng dòng. Phần "Dữ liệu" vẽ vòng số $0..9$: ô vàng là chữ số $x$ hiện tại, con trỏ y là chữ số cần về. Thử nút Vòng qua 9 → 0 để thấy phép % 10 làm việc thế nào nha.
Giải thích 1 số chỗ trong code ở trên:
if(fopen("KHOASO.INP", "r")) { freopen(...); }: nếu trong thư mục có fileKHOASO.INP(lúc thi trên máy chấm) thì chuyển việc đọc/ghi sang file; còn khi nộp lên hệ thống chấm online không có file này thì điều kiện sai và chương trình đọc bàn phím, in màn hình như bình thường. Viết kiểu này thì một code dùng được cho cả hai.- Hai hàm đặt tên
right,lefttrùng tên với hai "manipulator"std::right,std::leftcủa thư viện. Code vẫn biên dịch chạy đúng (mình đã thử với g++), vì khi gọiright(A, 2)với 2 tham số kiểuintthì trình biên dịch chọn đúng hàm của mình. Nhưng nếu bạn nào lo thì cứ đổi tên thànhxoayPhai,xoayTraicho chắc ăn nhé. - Thứ tự gọi các hàm trong một biểu thức (gọi
righthaylefttrước) do trình biên dịch quyết định; ở khung chạy tay mình cho chạy từ trái sang phải cho dễ theo dõi, kết quả không đổi.
Đánh giá độ phức tạp của cách làm này:
- Không gian: $O(1)$ – chỉ vài biến.
- Thời gian: mỗi lần gọi
right/leftlặp tối đa $9$ lần, có $8$ lần gọi, nên tối đa khoảng $72$ bước – là hằng số, $O(1)$.
Cách 2Công thức min(d, 10 − d) – O(1)
Ở cách 1 các bạn để ý: nếu gọi $d = |x - y|$ là khoảng cách "đi thẳng" giữa hai chữ số (không đi vòng qua $9 \to 0$), thì một chiều xoay tốn đúng $d$ lần, còn chiều ngược lại phải đi vòng qua phần còn lại của vòng tròn $10$ chữ số, tốn $10 - d$ lần. Ví dụ $8 \to 2$: $d = 6$ (đi trái $8 \to 7 \to \dots \to 2$), chiều kia $10 - 6 = 4$ (đi phải $8 \to 9 \to 0 \to 1 \to 2$). Vậy:
Khi $x = y$ thì $d = 0$, $\min(0, 10) = 0$ – đúng là không phải xoay. Cách này chỉ là "rút gọn" của cách 1 cho gọn code; trong phòng thi thì cách 1 vẫn là an toàn và đủ điểm, vì vòng lặp tối đa chỉ $9$ nấc.
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 cost(int x, int y){
int d = abs(x - y);
return min(d, 10 - d);
}
int main(){
if(fopen("KHOASO.INP", "r")){
freopen("KHOASO.INP", "r", stdin);
freopen("KHOASO.OUT", "w", stdout);
}
int A, B, C, D;
cin >> A >> B >> C >> D;
cout << cost(A, 2) + cost(B, 0) + cost(C, 2) + cost(D, 5) << endl;
}
import sys, os
def cost(x, y):
d = abs(x - y)
return min(d, 10 - d)
if os.path.exists("KHOASO.INP"):
sys.stdin = open("KHOASO.INP", "r")
sys.stdout = open("KHOASO.OUT", "w")
A, B, C, D = map(int, sys.stdin.read().split()[:4])
print(cost(A, 2) + cost(B, 0) + cost(C, 2) + cost(D, 5))
Đánh giá độ phức tạp của cách làm này: chỉ $4$ lần tính công thức.
- 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