Mã khóa
Đề bài
Để mở cửa kho báu, bạn cần tìm được chìa khóa bí mật. Quy tắc để xác định chìa khóa bí mật như sau: cho trước số nguyên dương $N$, chìa khóa bí mật $K$ là số nguyên dương lớn nhất không vượt quá $N$ và có số lượng ước số nguyên tố khác nhau nhiều nhất.
Yêu cầu: tìm chìa khóa bí mật $K$ tương ứng với số nguyên dương $N$ cho trước.
Dữ liệu vào: từ tệp văn bản KEY.INP gồm một dòng duy nhất chứa số nguyên dương $N$ $(2 \le N \le 10^6)$.
Kết quả: ghi ra tệp văn bản KEY.OUT số nguyên dương $K$.
Giới hạn: thời gian 0,3 giây (thầy đặt chặt hơn đề gốc 1 giây vì máy chấm CLUE rất mạnh), bộ nhớ 256 MB.
2928Giải thích ví dụ: trong các số từ $2$ tới $29$, số có nhiều ước nguyên tố khác nhau nhất có $2$ ước (ví dụ $6, 10, 12, \ldots, 28$). Số lớn nhất trong đó là $K = 28 = 2^2 \cdot 7$.
KEY.INP / KEY.OUT. Code C++ dùng if (fopen("KEY.INP", "r")) { freopen(...); freopen(...); }, bản Python dùng os.path.exists("KEY.INP"): có tệp thì đọc tệp, không có thì đọc bàn phím.Lời mở đầu
Bài này đếm "số ước nguyên tố khác nhau" (ký hiệu $\omega(x)$) cho mọi số từ $2$ tới $N$ rồi lấy số lớn nhất có $\omega$ lớn nhất. Cái khó nằm ở giới hạn 0,3 giây: phân tích từng số bằng thử chia sẽ không kịp. Mình đi từ cách thử chia từng số, tới sàng đếm ước nguyên tố, và cuối cùng là một mẹo toán học rất gọn: tích các số nguyên tố đầu tiên.
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 OJLộ trình lời giải, bấm vào từng phần để nhảy tới:
Bộ testBộ test chính thức có gì?
Đề không chia subtask, mình chia 100 test thành 4 nhóm theo $N$:
| Nhóm | Test | N | Đáp án K | N − K |
|---|---|---|---|---|
| 1 | 1 – 20 | 3 … 929 | 3 … 924 | 0 … 117 |
| 2 | 21 – 50 | 231 … 9.577 | 210 … 9.570 | 7 … 735 |
| 3 | 51 – 80 | 12.023 … 130.074 | 12.012 … 129.030 | 11 … 8.517 |
| 4 | 81 – 100 | 101.073 … 532.420 | 101.010 … 510.510 | 45 … 21.910 |
- $N$ lớn nhất trong bộ test là $532\,420$ (đề cho tới $10^6$). Nhiều đáp án rơi đúng vào các số đẹp như $30\,030 = 2\cdot3\cdot5\cdot7\cdot11\cdot13$ hay $510\,510 = 2\cdot3\cdot5\cdot7\cdot11\cdot13\cdot17$ – đó là gợi ý cho cách 3.
- Khoảng cách $N - K$ có thể lên tới hơn hai mươi nghìn, nên "đi lùi vài bước" không phải lúc nào cũng ngắn.
Ý tưởngω(x) lớn nhất đạt được là bao nhiêu?
Gọi $\omega(x)$ là số ước nguyên tố khác nhau của $x$. Nếu $\omega(x) = m$ thì $x$ chia hết cho $m$ số nguyên tố khác nhau, nên $x \ge$ tích của $m$ số nguyên tố nhỏ nhất:
Vậy $\omega$ lớn nhất trong đoạn $[2, N]$ đúng bằng $m$ lớn nhất với $P_m \le N$ (và $P_m$ là một số đạt được). Với $N \le 10^6$ thì $m \le 7$.
Như ví dụ $N = 29$: $P_2 = 6 \le 29 < P_3 = 30$ nên $\omega$ lớn nhất là $2$, và $K$ là số lớn nhất $\le 29$ có đúng $2$ ước nguyên tố: $29$ (nguyên tố, $\omega = 1$) → $28 = 2^2 \cdot 7$ ($\omega = 2$). Em nhập $N$ rồi bấm Tiếp để xem:
Cách 1Đếm ω(x) cho từng x bằng thử chia
Với mỗi $x$ từ $2$ tới $N$, phân tích $x$ bằng thử chia $p$ khi $p^2 \le x$, đếm số $p$ chia hết (mỗi $p$ chia hết thì chia hết luôn để $x$ không còn thừa số $p$). Giữ số $x$ có $\omega$ lớn nhất; dùng dấu >= để khi bằng nhau thì lấy số lớn hơn.
- Phân tích mỗi $x$ tốn tới $\sqrt x$ bước khi $x$ nguyên tố hoặc có ước lớn, nhưng phần lớn các số có ước nhỏ nên vòng thử thu nhỏ rất nhanh. Tổng cộng vẫn là $O(N\sqrt N)$ trong trường hợp xấu.
- Thực tế với $N \approx 5 \cdot 10^5$ (lớn nhất trong bộ test) bản C++ chạy khoảng 0,27 giây – sát nút giới hạn 0,3 giây; mình thử thêm $N = 10^6$ thì mất 0,53 giây, quá giới hạn. Bản Python không kịp ngay từ $N$ cỡ $10^5$.
Kết quả chạy thật: bản C++ đúng 100/100 test (Nhóm 1: 20/20, Nhóm 2: 30/30, Nhóm 3: 30/30, Nhóm 4: 20/20), test đúng chạy lâu nhất 0,268 giây; bản Python đúng 70/100 test (Nhóm 1: 20/20, Nhóm 2: 30/30, Nhóm 3: 20/30, Nhóm 4: 0/20), test đúng chạy lâu nhất 0,242 giây; 30 test quá thời gian.
Cách 2Sàng đếm số ước nguyên tố cho mọi x ≤ N
Biến thể của sàng Eratosthenes: mảng cnt[x] ban đầu bằng $0$. Duyệt $p = 2, 3, \ldots, N$; nếu cnt[p] == 0 thì chưa có số nguyên tố nhỏ hơn nào chia hết $p$, tức $p$ nguyên tố – ta cộng $1$ vào cnt của mọi bội của $p$. Cuối cùng cnt[x] $= \omega(x)$.
- Mỗi số nguyên tố $p$ duyệt $N/p$ bội; tổng $\sum_{p \le N} N/p \approx N \ln\ln N$ – chỉ vài triệu phép cộng.
- Mảng
cnt$10^6$ phần tử khai báo toàn cục. - Bản Python của cách này phải chạy vòng lặp hàng triệu bước bằng Python thuần, khó lọt 0,3 giây.
Kết quả chạy thật: bản C++ đúng 100/100 test (Nhóm 1: 20/20, Nhóm 2: 30/30, Nhóm 3: 30/30, Nhóm 4: 20/20), test đúng chạy lâu nhất 0,013 giây; bản Python đúng 90/100 test (Nhóm 1: 20/20, Nhóm 2: 30/30, Nhóm 3: 30/30, Nhóm 4: 10/20), test đúng chạy lâu nhất 0,132 giây; 10 test quá thời gian.
Cách 3Tính m từ tích các số nguyên tố đầu, rồi đi lùi từ N
Dùng nhận xét ở phần Ý tưởng: nhân dần $2 \cdot 3 \cdot 5 \cdots$ khi tích còn $\le N$ để có $m$ – số ước nguyên tố nhiều nhất có thể. Sau đó đi lùi $K = N, N - 1, \ldots$ và dừng ở số đầu tiên có $\omega(K) = m$ (phân tích bằng thử chia). Không cần mảng, và thường chỉ phải kiểm tra vài trăm tới vài chục nghìn số.
- Vòng nhân chỉ chạy tối đa 7 lần với $N \le 10^6$; mảng 10 số nguyên tố đầu là đủ dùng.
- Số bước lùi phụ thuộc $N$: trong bộ test nhiều nhất là $21\,910$ bước, mỗi bước phân tích tốn tối đa $\sqrt{N} \approx 1000$ phép chia, nhưng đa số dừng sớm.
- Cách này không cần mảng nên bản Python cũng nhẹ hơn hẳn cách 2.
Kết quả chạy thật: bản C++ đúng 100/100 test (Nhóm 1: 20/20, Nhóm 2: 30/30, Nhóm 3: 30/30, Nhóm 4: 20/20), test đúng chạy lâu nhất 0,021 giây; bản Python đúng 97/100 test (Nhóm 1: 20/20, Nhóm 2: 30/30, Nhóm 3: 30/30, Nhóm 4: 17/20), test đúng chạy lâu nhất 0,188 giây; 3 test quá thời gian.
Tổng kếtKết quả chạy thật trên bộ test
Mình chấm cả 3 cách (C++ và Python) trên toàn bộ 100 test với giới hạn 0,3 giây, đo thời gian CPU:
| Lời giải | Nhóm 1 test 1–20 | Nhóm 2 test 21–50 | Nhóm 3 test 51–80 | Nhóm 4 test 81–100 | Tổng | CPU lớn nhất (test đúng) |
|---|---|---|---|---|---|---|
| Cách 1 – C++ | 20/20 | 30/30 | 30/30 | 20/20 | 100/100 | 0,268s |
| Cách 1 – Python | 20/20 | 30/30 | 20/30 | 0/20 | 70/100 | 0,242s |
| Cách 2 – C++ | 20/20 | 30/30 | 30/30 | 20/20 | 100/100 | 0,013s |
| Cách 2 – Python | 20/20 | 30/30 | 30/30 | 10/20 | 90/100 | 0,132s |
| Cách 3 – C++ | 20/20 | 30/30 | 30/30 | 20/20 | 100/100 | 0,021s |
| Cách 3 – Python | 20/20 | 30/30 | 30/30 | 17/20 | 97/100 | 0,188s |
- 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