NUM5 (final2_2122_num5)
Đề bài
2. num5
Một số được gọi là NUM5 nếu số đó có ước nguyên tố lớn nhất không vượt quá 5. Ví dụ số NUM5 là: $3, 10$; số không phải số NUM5 là: $7, 44$. Hỏi với một số nguyên $N$, hãy kiểm tra xem $N$ có phải là số NUM5 không và có bao nhiêu số NUM5 không lớn hơn $N$.
Dữ liệu: Vào từ file văn bản NUM5.INP có khuôn dạng sau:
- Dòng đầu tiên gồm một số nguyên dương $T$ là số bộ test $(T \le 10^5)$;
- $T$ dòng sau, mỗi dòng gồm một số nguyên dương $N$ mô tả một bộ test $(N \le 10^9)$.
Kết quả: Ghi ra file văn bản NUM5.OUT gồm $T$ dòng, mỗi dòng in kết quả của một bộ test tương ứng có cấu trúc như sau:
- Nếu $N$ không phải là số NUM5 thì in ra
"NO"; - Ngược lại, in ra
"YES x"với $x$ là số lượng số NUM5 không lớn hơn $N$.
Ràng buộc:
- Có 30% số test tương ứng với 30% số điểm có $T \le 10^2$; $N \le 10^6$;
- 20% số test khác tương ứng với 20% số điểm có $T \le 10^5$; $N \le 10^6$;
- 50% số test còn lại tương ứng với 50% số điểm không có ràng buộc gì thêm.
Trên hệ thống chấm online thì đọc dữ liệu từ bàn phím và in ra màn hình.
4
1
10
7
44YES 1
YES 9
NO
NOGiải thích: khi $N = 10$, các số NUM5 thoả mãn: $1, 2, 3, 4, 5, 6, 8, 9, 10$.
Lờ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 chia sẻ đề bài để các bạn thử sức làm, kèm theo lời giải chi tiết để bạn nào không giải quyết được thì có thể tham khảo nhen. Hi vọng mang đến được giá trị cho các bạn 😇.
Giải thích đề cho bạn nào chưa hiểu: số NUM5 là số mà khi phân tích ra thừa số nguyên tố thì chỉ có các thừa số 2, 3, 5. Ví dụ $10 = 2 \cdot 5$ là NUM5; $44 = 2^2 \cdot 11$ có ước nguyên tố 11 > 5 nên không phải; $7$ là số nguyên tố lớn hơn 5 nên cũng không phải. Riêng số $1$ không có ước nguyên tố nào, đề tính nó là NUM5 (ví dụ $N = 1$ in YES 1). Với mỗi $N$: nếu $N$ là NUM5 thì in YES kèm số lượng số NUM5 từ 1 đến $N$, ví dụ $N = 10$ có 9 số $1, 2, 3, 4, 5, 6, 8, 9, 10$ ⇒ YES 9; nếu không thì chỉ in NO.
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:
Cách 1Duyệt 3 vòng lặp cho mỗi truy vấn – O(T · K)
Số có các ước nguyên tố $\le 5$ thì nó chỉ gồm 2, 3, 5 thôi. Có nghĩa là số đó có dạng:
Vậy duyệt 3 vòng for lồng nhau $p_2$, $p_3$, $p_5$ là sinh ra được hết các số NUM5 $\le N$: vừa đếm được có bao nhiêu số, vừa kiểm tra được trong đó có số nào bằng đúng $N$ không. Vì mỗi bộ $(p_2, p_3, p_5)$ cho ra một số khác nhau (phân tích thừa số nguyên tố là duy nhất) nên đếm như vậy không bị trùng.
Với $N \le 10^9$ thì $p_2$ chỉ có tầm 30 giá trị ($2^{29} < 10^9 < 2^{30}$), $p_3$ khoảng 19 giá trị, $p_5$ khoảng 13 giá trị. Ước lượng thô theo kiểu nhân hết lại thì khoảng vài nghìn bộ cho mỗi truy vấn, mà có tối đa $T = 10^5$ truy vấn ⇒ cỡ $10^8$ đến $4 \cdot 10^8$ phép tính. Cũng căng, có khả năng TLE vì ngưỡng 1 giây chỉ khoảng $10^8$ thôi. Cần tối ưu lại một chút (xem cách 2).
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);
int t;
cin >> t;
while (t--) {
long long n;
cin >> n;
int cnt = 0;
bool isNum5 = false;
for (long long x = 1; x <= n; x *= 2) { // x = 2^p2
for (long long y = x; y <= n; y *= 3) { // y = 2^p2 * 3^p3
for (long long z = y; z <= n; z *= 5) { // z = 2^p2 * 3^p3 * 5^p5
cnt++;
if (z == n) {
isNum5 = true;
}
}
}
}
if (isNum5) {
cout << "YES " << cnt << "\n";
}
else {
cout << "NO\n";
}
}
return 0;
}
import sys
def main():
data = sys.stdin.buffer.read().split()
t = int(data[0])
out = []
for k in range(1, t + 1):
n = int(data[k])
cnt = 0
is_num5 = False
x = 1
while x <= n: # x = 2^p2
y = x
while y <= n: # y = 2^p2 * 3^p3
z = y
while z <= n: # z = 2^p2 * 3^p3 * 5^p5
cnt += 1
if z == n:
is_num5 = True
z *= 5
y *= 3
x *= 2
out.append("YES " + str(cnt) if is_num5 else "NO")
print("\n".join(out))
main()
Đánh giá độ phức tạp: gọi $K$ là số lượng số NUM5 $\le 10^9$ ($K = 1530$). Mỗi truy vấn tốn $O(K)$ ⇒ tổng $O(T \cdot K)$, bộ nhớ $O(1)$.
Cách 2Sinh hết số NUM5 ≤ 10^9, sắp xếp rồi chặt nhị phân – O(K log K + T log K)
Để ý cách 1 làm đi làm lại cùng một việc cho mọi truy vấn. Vậy đầu tiên ta sinh ra toàn bộ các số $\le 1$ tỷ mà chỉ có 3 ước nguyên tố 2, 3, 5 (chỉ làm một lần), xong rồi sắp xếp tụi nó lại. Với mỗi số $N$, ta chặt nhị phân tìm vị trí của $N$ trong mảng:
- Nếu tìm thấy $N$ ở vị trí
pos(đánh số từ 0) thì $N$ là NUM5, và các số NUM5 $\le N$ chính là các phần tử từ đầu mảng đến vị trí đó ⇒ cópos + 1số ⇒ inYES pos+1. - Không thấy thì in
NO.
Các bạn nhìn bản nháp này sẽ rõ, mỗi dòng là một bộ $(p_2, p_3, p_5)$ và số tương ứng (mình làm mẫu nhanh, còn thiếu nhiều bộ, nhưng đại ý là cứ xét hết những số $\le 1$ tỷ rồi từ đó chặt nhị phân):
| p2 p3 p5 | Số | p2 p3 p5 | Số | p2 p3 p5 | Số |
|---|---|---|---|---|---|
| 0 0 1 | 5 | 0 1 0 | 3 | 1 0 0 | 2 |
| 0 0 2 | 25 | 0 2 0 | 9 | 2 0 0 | 4 |
| 0 0 3 | 125 | 0 3 0 | 27 | 3 0 0 | 8 |
| 0 1 1 | 1 · 3 · 5 = 15 | 0 2 1 | 1 · 9 · 5 = 45 | 1 1 1 | 2 · 3 · 5 = 30 |
Em nhập $N$ (từ 1 đến 2000) rồi bấm Tiếp để xem mảng các số NUM5 đã sắp xếp và quá trình chặt nhị phân (lower_bound) tìm $N$:
Source code giải quyết theo tư tưởng mình đã trình bày ở trên và cũng đã nộp thành công trên web, các bạn xem để được hiểu rõ hơn:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
const long long Max = 1e9;
vector<int> a;
for(int p2 = 0; ; ++p2){
long long x = pow(2, p2);
if(x > Max){
break;
}
for(int p3 = 0; ; ++p3){
long long y = pow(3, p3);
if(x * y > Max){
break;
}
for(int p5 = 0; ; ++p5){
long long z = pow(5, p5);
if(x * y * z > Max){
break;
}
a.push_back(x * y * z);
}
}
}
sort(a.begin(), a.end());
int t;
cin >> t;
while(t--){
int n;
cin >> n;
auto it = lower_bound(a.begin(), a.end(), n);
if(*it != n){
cout << "NO" << endl;
}
else{
cout << "YES " << it - a.begin() + 1 << endl;
}
}
return 0;
}
import sys
from bisect import bisect_left
def main():
MAX = 10 ** 9
a = []
x = 1
while x <= MAX: # x = 2^p2
y = x
while y <= MAX: # y = 2^p2 * 3^p3
z = y
while z <= MAX: # z = 2^p2 * 3^p3 * 5^p5
a.append(z)
z *= 5
y *= 3
x *= 2
a.sort()
data = sys.stdin.buffer.read().split()
t = int(data[0])
out = []
for k in range(1, t + 1):
n = int(data[k])
pos = bisect_left(a, n)
if pos < len(a) and a[pos] == n:
out.append("YES " + str(pos + 1))
else:
out.append("NO")
print("\n".join(out))
main()
Giải thích một số chỗ trong code:
- Mảng
akiểuintlà đủ vì mọi số đưa vào đều $\le 10^9$; còn các biến tíchx,y,zvà phép nhânx * y * zphải dùnglong long, vì tích có thể vượt $10^9$ trước khi bịbreak. *itluôn hợp lệ: số lớn nhất trong mảng là $2^9 \cdot 5^9 = 10^9$ đúng bằng giới hạn của $N$, nênlower_boundkhông bao giờ trả vềa.end(). Bản Python mình vẫn kiểm tra thêmpos < len(a)cho chắc.- Đề gốc đọc/ghi file
NUM5.INP/NUM5.OUT: nếu nộp ở kỳ thi thì các bạn thêm 2 dòngfreopen("NUM5.INP", "r", stdin);vàfreopen("NUM5.OUT", "w", stdout);ở đầu hàmmain. Trên HNOJ thì đọc/ghi chuẩn như code ở trên.
Đánh giá độ phức tạp của cách làm này:
- Không gian: $O(K)$ với $K = 1530$ số, rất nhỏ.
- Thời gian: sinh mảng $O(K)$, sắp xếp $O(K \log K)$, chỉ làm một lần. Mỗi truy vấn chặt nhị phân $O(\log K)$, khoảng 11 bước ⇒ tổng $O(K \log K + T \log K)$, cỡ $10^6$ phép tính, chạy tức thì.
endl sau mỗi dòng, lệnh này bắt chương trình đẩy dữ liệu ra ngay (flush) nên chậm hơn "\n". Với $10^5$ dòng thì vẫn ổn, nhưng các bạn nên quen dùng "\n" khi in nhiều dòng nhé.YES 1530), $N = 999\,999\,999$ (NO) cũng đúng. Với $T = 10^5$, $N$ đến $10^9$: cách 1 bản C++ khoảng 0.2 giây, bản Python khoảng 23 giây; cách 2 cả C++ và Python đều dưới 0.2 giây.- 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