Professional Documents
Culture Documents
MỤC LỤC
CHƯƠNG 1. CÁC BƯỚC GIẢI BÀI TOÁN TIN HỌC ..................................................................... 5
1.1. Bài toán tin học .................................................................................................................................. 5
1.2. Xác định bài toán ............................................................................................................................... 5
Bài tập áp dụng 1.2 ............................................................................................................................. 5
1.3. Thiết kế thuật toán............................................................................................................................. 6
1.3.1. Khái niệm thuật toán (algorithm) ................................................................................................. 6
1.3.2. Các tính chất của thuật toán .......................................................................................................... 7
1.3.3. Biểu diễn thuật toán ...................................................................................................................... 8
Liệt kê các bước thực thi..................................................................................................................... 8
Sơ đồ thuật toán .................................................................................................................................. 8
Bài tập áp dụng 1.3.3. ......................................................................................................................... 9
1.3.4. Các cấu trúc cơ bản của thuật toán ............................................................................................. 10
Định lý Bohn-Jacopini ...................................................................................................................... 10
Các cấu trúc của thuật toán ............................................................................................................... 10
BÀI TẬP THIẾT KẾ SƠ ĐỒ THUẬT TOÁN ..................................................................................... 14
1.4. Lập trình C/C++ (chuyển đổi sơ đồ thành chương trình)............................................................ 17
1.4.1. Nguyên tắc chung ....................................................................................................................... 17
1.4.2. Biến và kiểu dữ liệu .................................................................................................................... 18
1.4.3. Hằng số (constant) ...................................................................................................................... 19
1.4.4. Câu lệnh gán (assignment) ......................................................................................................... 20
1.4.5. Các phép toán ............................................................................................................................. 21
1.4.5.1. Phép toán số học .................................................................................................................. 21
1.4.5.2. Phép toán quan hệ ................................................................................................................ 22
1.4.5.3. Phép toán logic..................................................................................................................... 22
1.4.5.4. Phép gán phức hợp (compound) .......................................................................................... 23
1.4.5.5. Phép tăng/giảm giá trị của biến............................................................................................ 23
1.4.6. Chuyển đổi các cấu trúc thuật toán thành câu lệnh .................................................................... 24
1.4.6.1. Câu lệnh nhập/xuất .............................................................................................................. 24
1.4.6.2. Lệnh rẽ nhánh ...................................................................................................................... 27
1.4.6.3. Lệnh lặp ............................................................................................................................... 29
1.4.7. Hàm (function) ........................................................................................................................... 31
Ví dụ 1.2.1:
Cho 2 số nguyên 𝑎, 𝑏. Tính tổng của chúng.
Input: 𝑎, 𝑏 ∈ 𝑍
Output: 𝑐 ∈ 𝑍
Có 2 input và 1 output
Ví dụ 1.2.2:
Cho số nguyên 𝑛. Kiểm tra 𝑛 có phải là số dương chẵn?
Input: 𝑛 ∈ 𝑍
Output: true/false
Có 1 input và 1 output
Xác định input, output và số lượng của chúng ở mỗi bài toán.
1) Cho số nguyên dương 𝑛. Đếm số ước dương của 𝑛.
2) Cho số nguyên 𝑛. Kiểm tra 𝑛 có phải là số nguyên tố?
3) Cho 2 số nguyên 𝑎, 𝑏. Tìm 𝑈𝐶𝐿𝑁(𝑎, 𝑏).
4) Cho số nguyên dương 𝑛. Liệt kê tất cả số chính phương có giá trị không vượt quá 𝑛.
Ví dụ 1.3.1:
Cho 3 số nguyên 𝑎, 𝑏, 𝑐. Tìm giá trị lớn nhất.
Thuật toán:
Bước 1: Input 𝑎, 𝑏, 𝑐
Bước 2: Gán 𝑚𝑎𝑥𝑉𝑎𝑙𝑢𝑒 = 𝑎
Bước 3: Kiểm tra 𝑏 > 𝑚𝑎𝑥𝑉𝑎𝑙𝑢𝑒
- Nếu đúng thì gán 𝑚𝑎𝑥𝑉𝑎𝑙𝑢𝑒 = 𝑏
Bước 4: Kiểm tra 𝑐 > 𝑚𝑎𝑥𝑉𝑎𝑙𝑢𝑒
- Nếu đúng thì gán 𝑚𝑎𝑥𝑉𝑎𝑙𝑢𝑒 = 𝑐
Bước 5: Output 𝑚𝑎𝑥𝑉𝑎𝑙𝑢𝑒. Kết thúc.
Ghi chú: Thao tác gán 𝑎 = 𝑏 gán giá trị của 𝑏 cho 𝑎, nghĩa là sau thao tác, giá trị của 𝑎 sẽ bằng giá trị
của 𝑏. Ví dụ sau thao tác 𝑎 = 𝑎 + 5 thì giá trị của 𝑎 sẽ tăng thêm 5.
Ví dụ 1.3.2:
Cho 3 số nguyên 𝑎, 𝑏, 𝑐. Đếm số lượng số chẵn trong 3 số.
Thuật toán:
Bước 1: Input 𝑎, 𝑏, 𝑐.
Bước 2: Gán 𝑐𝑛𝑡 = 0
Bước 3: Kiểm tra 𝑎 mod 2 = 0?
- Nếu đúng thì 𝑐𝑛𝑡 = 𝑐𝑛𝑡 + 1 (tăng cnt thêm 1 đơn vị)
Bước 4: Kiểm tra 𝑏 mod 2 = 0?
- Nếu đúng thì 𝑐𝑛𝑡 = 𝑐𝑛𝑡 + 1 (tăng cnt thêm 1 đơn vị)
Ví dụ 1.3.3:
Cho số nguyên dương 𝑛. Đếm số lượng ước dương của 𝑛.
Cách 1:
- Nhận xét: Nếu 𝑎 là ước dương của 𝑛 thì 1 ≤ 𝑎 ≤ 𝑛. Do đó, để đếm số ước dương của 𝑛 ta xét 𝑎
lần lượt mang các giá trị từ 1 đến 𝑛. Với mỗi giá trị của 𝑎, kiểm tra xem 𝑛 có chia hết cho 𝑎.
- Số trường hợp phải xét: 𝑛.
Cách 2:
- Nhận xét: Nếu 𝑎 là ước dương của 𝑛 thì luôn tồn tại số nguyên dương 𝑏 cũng là ước dương của 𝑛
sao cho 𝑎 × 𝑏 = 𝑛, khi đó 𝑏 = [𝑛/𝑎] (kí hiệu [𝑥] với ý nghĩa lấy giá trị phần nguyên của số thực
𝑥).
- Giả sử 𝑎 ≤ 𝑏 ⟹ 𝑎2 ≤ 𝑎 × 𝑏 = 𝑛 ⟹ 𝑎2 ≤ 𝑛 ⟹ 1 ≤ 𝑎 ≤ [√𝑛]. Như vậy khi 𝑎 là ước của 𝑛 thì
ta tìm được 2 ước là 𝑎 và 𝑏(𝑎 < 𝑏). Khi nào xảy ra trường hợp 𝑎 = 𝑏?
- Số trường hợp phải xét: [√𝑛].
Ví dụ 1.3.4:
Cho số nguyên dương 𝑛. Giải phương trình nghiệm nguyên không âm 2𝑥 + 𝑦 = 𝑛(∗).
Việc giải phương trình trên chính là liệt kê tất cả cặp giá trị (𝑥, 𝑦) nguyên không âm thỏa (*).
Từ phương trình (∗) ta có 0 ≤ 𝑥 ≤ [𝑛/2] và 0 ≤ 𝑦 ≤ 𝑛.
Cách 1:
- Thử lần lượt từng cặp giá trị (𝑥, 𝑦) trong miền giá trị của chúng và kiểm tra thỏa (∗).
- Số trường hợp phải xét: ([𝑛/2] + 1) × (𝑛 + 1).
Cách 2:
Mô tả thuật toán dưới dạng các bước thi hành, mỗi bước chỉ ra các thao tác thi hành cụ thể để xử lý input
tìm ra output. Thuật toán sẽ thực thi lần lượt từng bước theo đúng trình tự mô tả.
Ví dụ 1.3.5:
Thuật toán giải và biện luận phương trình bậc 1: 𝑎𝑥 + 𝑏 = 0
Bước 1: Input 𝑎, 𝑏
Bước 2: Kiểm tra 𝑎 = 0?
- Nếu đúng thì đến bước 3.
- Ngược lại thì thông báo nghiệm –b/a. Đến bước 4.
Bước 3: Kiểm tra 𝑏 = 0?
- Nếu đúng thì thông báo phương trình vô số nghiệm. Đến bước 4.
- Ngược lại thông báo phương trình vô nghiệm. Đến bước 4.
Bước 4: Kết thúc
Sơ đồ thuật toán
Begin
End
Biểu diễn thao tác nhập/xuất dữ liệu Biểu diễn phép gán hoặc tính toán
False True
Biểu thức logic
Biểu diễn thao tác kiểm tra logic (kiểm tra đúng/sai)
Ví dụ 1.3.6:
Sơ đồ thuật toán giải và biện luận phương trình bậc 1: 𝑎𝑥 + 𝑏 = 0
Begin
𝑎, 𝑏
False True
𝑎=0
False True
𝑏=0
−𝑏/𝑎
End
Mọi quá trình tính toán đều có thể được biểu diễn dựa trên 3 cấu trúc cơ bản của thuật toán: cấu trúc tuần
tự, cấu trúc rẽ nhánh, cấu trúc lặp.
Begin
Begin
Input
Input …
Xử lý 1
False True
ĐK rẽ nhánh
… …
Output
Output
End
End
Begin
Input
… False True
Output ĐK lặp
Ví dụ 1.3.7:
Cho số nguyên dương 𝑛. Thiết kế sơ đồ thuật toán tính giá trị biểu thức
1 1
𝑆 = 1 + + ⋯+
2 𝑛
Input: 𝑛 ∈ ℤ+
Output: 𝑆 ∈ ℝ
Giải:
Do biểu thức có 𝑛 phân số, phân số thứ 𝑖(𝑖 = 1,2,3, … , 𝑛) có giá trị 1/𝑖. Giá trị của biểu thức 𝑆 được
tính bằng cách cộng dồn từng phân số vào kết quả nên quá trình tính toán được biểu diễn bằng cấu
trúc lặp 𝑛 lần như sau
- Khởi tạo lặp
𝑆=0
𝑖=1
- Điều kiện lặp
𝑖≤𝑛
- Thao tác lặp
1
𝑆=𝑆+
𝑖
𝑖 =𝑖+1
Begin
𝑆=0
𝑖=1
F T
𝑖≤𝑛
𝑆 𝑆 = 𝑆 + 1/𝑖
End 𝑖 =𝑖+1
Ví dụ 1.3.8:
Cho số nguyên dương 𝑛. Thiết kế sơ đồ thuật toán tính giá trị biểu thức
1 1
𝑆 = 1 − + ⋯ + (−1)𝑛+1
2 𝑛
Input: 𝑛 ∈ ℤ+
Output: 𝑆 ∈ ℛ
Giải:
Tương tự ví dụ 1.3.7, biểu thức có 𝑛 phân số, phân số thứ 𝑖(𝑖 = 1,2,3, … , 𝑛) có giá trị
1
(−1)𝑖+1
𝑖
Tuy nhiên thuật toán này không hiệu quả. Nhận xét (−1)𝑖+1 có giá trị 1 nếu 𝑖 lẻ, ngược lại có giá trị
−1 nếu 𝑖 chẵn. Ta có sơ đồ thuật toán hiệu quả hơn như sau
Begin
𝑆=0
𝑖=1
F T
𝑖≤𝑛
𝑆 F T
𝑖 𝑚𝑜𝑑 2 = 0
𝑆 = 𝑆 + 1/𝑖 𝑆 = 𝑆 − 1/𝑖
End
𝑖 =𝑖+1
𝑎) 𝑆 = 1 + √2 + ⋯ + √𝑛
1 2 𝑛−1
𝑏) 𝑆 = + + ⋯+
2 3 𝑛
1 1
𝑐) 𝑆 = 1 − + ⋯ + (−1)𝑛+1
2! 𝑛!
𝑑) 𝑆 = √2 + √2 + ⋯ + √2 (𝑛 dấu căn)
1 ∗ 3 ∗ … ∗ 𝑛, nếu 𝑛 lẻ
𝑒) 𝑆 = {
2 ∗ 4 ∗ … ∗ 𝑛, nếu 𝑛 chẵn
1
𝑓) 𝑆 = 1 +
1
2+
3 + ⋯+1
𝑛
2) Cho số nguyên 𝑘 và 2 số nguyên dương 𝑛, 𝑚. Tính giá trị biểu thức sau:
𝑎) 𝑆 = (1! + 2! + ⋯ + 𝑛!) mod 𝑚
𝑏) 𝑆 = (𝑘 𝑛 + 𝑘 𝑛−1 + ⋯ + 1) mod 𝑚
3) Cho số nguyên dương 𝑛 và số thực 𝑥. Tính giá trị các biểu thức:
𝑥2 𝑛+1
𝑥𝑛
𝑎) 𝑆 = 𝑥 − + … + (−1)
2! 𝑛!
𝑥 𝑛−2 𝑥 1
𝑏) 𝑆 = 𝑥 𝑛−1 + +⋯+ +
2! (𝑛 − 1)! 𝑛!
4) Cho số nguyên dương 𝑛, tìm số nguyên dương 𝑥 nhỏ nhất thỏa bất đẳng thức
1 + √2 + √3 + ⋯ + √𝑥 ≥ 𝑛
5) Cho 𝑛 điện trở giống nhau 𝑅1 = 𝑅2 = 𝑅3 = ⋯ = 𝑅𝑛 = 𝑘(Ω),
trong đó 𝑘 là một số thực dương. Người ta mắc các điện trở này
thành một mạch điện theo quy luật như hình vẽ.
Cho số nguyên 𝑛 và số thực dương 𝑘. Hãy tính tổng điện trở của
mạch điện được nối theo quy luật trên.
𝐹1 = 1
6) Dãy số nguyên {𝐹𝑖 } được định nghĩa như sau:{𝐹2 = 1
𝐹𝑛 = 𝐹𝑛−1 + 𝐹𝑛−2 , 𝑛 ≥ 3
Cho số nguyên dương 𝑛. Thiết kế thuật toán tìm giá trị của 𝐹𝑛 .
7) Dãy số nguyên {𝐹𝑛 } được định nghĩa như sau:
𝐹𝑛 = 𝑛(𝐹1 + 𝐹2 + ⋯ + 𝐹𝑛−1 )
int main()
{
<Thân chương trình chính>;
return 0;
}
Trước khi viết chương trình trên môi trường Code::Blocks, ta cần phải tạo một project để chứa chương
trình.
Ví dụ một chương trình xuất ra màn hình thông điệp Welcome to C/C++ language!
Output
Mỗi câu lệnh của C/C++ đều thuộc một thư viện nào đó nên việc khai báo thư viện là cần phải có trong
một chương trình. Một số thư viện thông dụng của C/C++ thuộc danh sách các thư viện chuẩn (Standard
Library)
1 <iostream> C++ Chứa các lệnh nhập/xuất luồng chuẩn của C++
để lưu trữ giá trị trong chương trình. phải được xác định một kiểu dữ liệu tương ứng. Như vậy,
Các giá trị trong chương trình gồm có: input, kiểu dữ liệu xác định miền giá trị mà biến được phép lưu
output và các giá trị tính toán trung gian. trữ và kích thước vùng nhớ RAM dành để lưu giá trị cho
chương trình.
Cú pháp khai báo biến: <kiểu dữ liệu> <tên biến>;
Có 4 loại kiểu dữ liệu cơ sở: kiểu số nguyên, kiểu số thực, kiểu kí tự và kiểu luận lý (kiểu logic). Các kiểu
dữ liệu cơ sở thông dụng được cho trong bảng sau:
Lưu ý
- Các kí tự được lưu trữ bên trong máy tính dưới dạng các giá trị nguyên chính là mã ASCII tương
ứng. Chẳng hạn 'A' có mã ASCII 65, 'b' có mã ASCII 98, …
- Các giá trị false, true của kiểu luận lý cũng được lưu trữ dưới dạng số nguyên với false tương
ứng 0 và true tương ứng với 1.
Ví dụ 1.4.1: Khai báo biến 𝑎 kiểu số nguyên có dấu kích thước 32 bits:
int a;
Ví dụ 1.4.2: Khai báo 3 biến số thực 𝑥, 𝑦, 𝑧:
double x, y, z;
Ví dụ 1.4.3: Khai báo biến 𝑎 kiểu nguyên không dấu kích thước 64 bits:
unsigned long long b; Ta có thể sử dụng định nghĩa kiểu typedef để làm gọn
câu lệnh khai báo:
Ví dụ 1.4.4: Khai báo biến 𝑎 sau khi định nghĩa kiểu
Mẹo
Lưu ý
- Giá trị của biểu thức được gán cho biến phải là loại dữ liệu có cùng kiểu với biến.
- Giá trị của biểu thức được gán phải nằm trong miền giá trị giới hạn của biến.
Phép toán số học là phép toán được áp dụng để thực hiện các thao tác tính toán số học đối với các giá trị
hoặc biến có kiểu dữ liệu là số nguyên hoặc số thực. Biểu thức có chứa các phép toán số học được gọi là
biểu thức số học.
Bảng dưới đây liệt kê các phép toán số học theo thứ tự độ ưu tiên từ cao xuống thấp:
Phép toán quan hệ được dùng để so sánh hai giá trị hoặc biến. Biểu thức có chứa phép toán quan hệ được
gọi là biểu thức logic. Miền giá trị của biểu thức logic là false hoặc true.
Phép toán quan hệ thường được sử dụng để thiết lập điều kiện rẽ nhánh hoặc điều kiện lặp trong lập trình.
> Kiểm tra toán hạng bên trái lớn hơn toán hạng bên phải
< Kiểm tra toán hạng bên trái nhỏ hơn toán hạng bên phải
>= Kiểm tra toán hạng bên trái lớn hơn hoặc bằng toán hạng bên phải
<= Kiểm tra toán hạng bên trái nhỏ hơn hoặc bằng toán hạng bên phải
Phép toán logic được dùng để kết hợp các biểu thức logic. Có 3 toán tử logic: AND (phép và, kí hiệu &&),
OR (phép hoặc, kí hiệu ||), NOT (phép phủ định, kí hiệu !).
Xét 2 toán hạng 𝐴 và 𝐵 chỉ nhận giá trị false hoặc true, bảng chân trị của các phép toán logic được
cho trong bảng sau:
A B !A A & B A || B
Ví dụ 1.4.15: Biểu thức kiểm tra giá trị 𝑥 có thuộc đoạn [𝑎, 𝑏] hay không
(x >= a && x <= b)
Ví dụ 1.4.16: Biểu thức kiểm tra giá trị 𝑥 có nằm ngoài đoạn [𝑎, 𝑏] hay không
(x < a || x > b) hoặc !(x >= a && x <= b)
Ví dụ 1.4.17: Biểu thức kiểm tra 3 giá trị 𝑎, 𝑏, 𝑐 có bằng nhau không
(a == b && b == c)
1 x += a x = x + a
2 x -= a x = x - a
3 x *= a x = x * a
4 x /= a x = x / a
5 x %= a x = x % a
1 ++x Tăng giá trị của biến thêm 1 đơn vị trước khi thi hành câu lệnh.
2 --x Giảm giá trị của biến đi 1 đơn vị trước khi thi hành câu lệnh.
3 x++ Tăng giá trị của biến thêm 1 đơn vị sau khi thi hành câu lệnh.
4 x-- Giảm giá trị của biến đi 1 đơn vị sau khi thi hành câu lệnh.
Ví dụ 1.4.19:
a = 5, b = 10;
x = ++a, y = --b;
sau đoạn lệnh các biến sẽ có giá trị như sau: a = 6, b = 9, x = 6, y = 9
Ví dụ 1.4.20:
a = 5, b = 10;
c = a++ + b--;
sau đoạn lệnh các biến sẽ có giá trị như sau: a = 6, b = 9, c = 15.
1.4.6. Chuyển đổi các cấu trúc thuật toán thành câu lệnh
1.4.6.1. Câu lệnh nhập/xuất
Lệnh nhập dữ liệu thực hiện input dữ liệu cho chương trình từ thiết bị nhập chuẩn (bàn phím hoặc tập
tin).
Cú pháp lệnh nhập liệu:
- Ngôn ngữ C: scanf("<đặc tả dữ liệu nhập>", <danh sách biến>);
- Ngôn ngữ C++: cin>><danh sách biến>;
Chức năng: Đọc danh sách dữ liệu từ luồng nhập chuẩn (standard input: nhập từ bàn phím hoặc tập tin)
và lưu vào <danh sách biến> tương ứng. Khi sử dụng hàm nhập của ngôn C, ta cần phải thêm các kí
tự đặc tả dữ liệu nhập cho các biến tùy vào kiểu dữ liệu tương ứng của biến.
Một số kí tự đặc tả định dạng dữ liệu nhập (dành cho ngôn ngữ C) cho một số kiểu dữ liệu thường dùng
1 %d int
2 %u unsigned int
5 %lf double
Ví dụ 1.4.21:
Nhập 2 số nguyên từ bàn phím và lưu vào 2 biến tương ứng 𝑎, 𝑏
int a, b; int a, b;
scanf("%d%d", &a, &b); cin>>a>>b;
Ví dụ 1.4.22:
Nhập 1 số nguyên và một số thực từ bàn phím và lưu vào 2 biến tương ứng 𝑥, 𝑦
int x; int x;
double y; double y;
Lưu ý: Ta bắt buộc phải thêm kí tự & trước mỗi biến của hàm scanf.
Lệnh xuất dữ liệu thực hiện output dữ liệu ra thiết bị xuất chuẩn (màn hình hoặc tập tin).
Cú pháp lệnh xuất:
- Ngôn ngữ C: printf("<đặc tả>/<thông điệp xuất>", <danh sách biến>);
- Ngôn ngữ C++: cout<<"<thông điệp xuất>"/<danh sách biến>;
Chức năng: Xuất giá trị của <danh sách biến> ra luồng xuất chuẩn (standard output: xuất ra màn hình
hoặc tập tin). <thông điệp xuất> cần phải được đặt trong cặp dấu "". Tương tự như hàm scanf, ta
cần phải thêm các kí tự đặc tả dữ liệu xuất cho các biến tùy vào kiểu dữ liệu tương ứng của biến. Các kí
tự đặc tả dữ liệu xuất của hàm printf tương tự như các kí tự đặc tả của hàm scanf.
Dữ liệu xuất ra luồng chuẩn trong C có thể được điều khiển bằng các hằng kí tự tương tự như các hằng kí
tự đặc tả định dạng dữ liệu nhập.
Ví dụ 1.4.23:
Xuất giá trị của 2 biến nguyên 𝑎, 𝑏 ra màn hình và cách nhau khoảng trắng
Ví dụ 1.4.24:
Xuất giá trị của 3 biến nguyên 𝑎, 𝑏, 𝑐 ra màn hình, mỗi biến nằm trên một dòng
Ví dụ 1.4.25:
Xuất kết quả các phép tính: cộng, trừ, nhân và chia của số nguyên 𝑎 cho số nguyên 𝑏. Mỗi biểu thức
trên một dòng.
Ví dụ 1.4.26:
Xuất giá trị 2 biến số thực 𝑥, 𝑦 ra màn hình và cách nhau khoảng trắng, giá trị của mỗi biến được làm
tròn 3 chữ số thập phân
Lưu ý
- Để sử dụng hàm setprecision(3) trong C++ ta cần phải khai báo thư viện <iomanip>.
- Câu lệnh printf("The number is:%[d.k]lf", x) xuất số thực 𝑥 ra màn hình và được làm
tròn 𝑘 chữ số thập phân với 𝑑 vị trí dành trước. Kết quả xuất được minh họa như hình.
d
- Chuỗi <thông điệp xuất> có thể được chèn các kí tự điều khiển để định dạng kết quả xuất. Các
kí tự điều khiển được đặt trong chuỗi và bắt đầu bằng kí tự \. Một số kí tự điều khiển thông thường
Cấu trúc rẽ nhánh được sử dụng để đưa ra các quyết định thi hành trong lặp trình. Có 2 dạng rẽ nhánh
phổ dụng: rẽ nhánh đầy đủ và rẽ nhánh khuyết.
Rẽ nhánh đầy đủ
Sơ đồ thực thi
if (<ĐK rẽ nhánh>)
{
<Khối lệnh 1>;
false true }
ĐK rẽ nhánh else
{
Khối lệnh 2
<Khối lệnh 2>;
Khối lệnh 1
}
<Khối lệnh 1> được thực thi nếu <điều kiện rẽ nhánh> có giá trị true, ngược lại <Khối lệnh
2> được thực thi. Như vậy chỉ 1 trong 2 khối lệnh này được thực thi.
Ví dụ 1.4.27:
if (a % 2 == 0) else
{ {
a = a / 2; a = a*3 + 1;
++cnt; ++cnt;
} }
Lưu ý: Cặp dấu ngoặc được dùng để bao một khối lệnh. Nếu như khối lệnh chỉ có 1 câu lệnh thì ta có
thể bỏ chúng đi.
Ví dụ 1.4.28: Đoạn chương trình trên có thể được viết lại như sau
if (a % 2 == 0)
a = a / 2;
else a = a*3 + 1;
++cnt;
if (<ĐK rẽ nhánh>)
true {
ĐK rẽ nhánh
<Khối lệnh>;
}
Khối lệnh
false
<Khối lệnh> được thực thi nếu <điều kiện rẽ nhánh> có giá trị true. Rẽ nhánh khuyết được sử
dụng khi không cần quan tâm trường hợp <điều kiện rẽ nhánh> có giá trị false.
Ví dụ 1.4.29:
Cho 4 số nguyên 𝑎, 𝑏, 𝑐, 𝑑. Các số nguyên được đánh thứ tự tương ứng từ 1 đến 4. Hãy cho biết thứ
tự của số lớn nhất. Nếu có nhiều số lớn nhất thì tìm thứ tự nhỏ nhất.
Đoạn chương trình thực hiện
int maxValue = a, pos = 1;
if (b > maxValue)
{
maxValue = b;
pos = 2;
}
if (c > maxValue)
{
maxValue = c;
pos = 3;
}
if (d > maxValue)
{
maxValue = d;
pos = 4;
}
Ví dụ 1.4.30:
Cho số nguyên dương 𝑛. Tính tổng các chữ số của 𝑛
Ví dụ 1.4.31:
Cho số nguyên dương 𝑛. Tính giá trị biểu thức
𝑆 = √2 + √2 + ⋯ + √2 (𝑛 𝑑ấ𝑢 𝑐ă𝑛)
Phân tích:
Input: 𝑛, 𝑎 ∈ ℤ+ .
Output: numDays ∈ ℤ+ là số ngày nuôi cấy tối thiểu.
Thao tác lặp: Sau mỗi ngày nuôi cấy các cá thể virus nhân đôi số lượng, biểu thức biểu diễn:
𝑎 =𝑎∗2
numDays = numDays + 1
Khởi tạo lặp: Khi chưa bắt đầu nuôi cấy thì số ngày đếm được là 0, biểu thức biểu diễn: numDays = 0
Điều kiện lặp: Điều kiện còn tiếp tục nuôi cấy (khi số virus đang nuôi cấy chưa đủ 𝑛), biểu thức biểu diễn:
𝑎 < 𝑛.
Đoạn chương trình thực hiện:
int numDays = 0;
while (a < n)
{
a = a*2;
numDays = numDays + 1;
}
𝑆 = 1 + √2 + ⋯ + √𝑛
Đoạn chương trình thực hiện:
double S = 0;
for (int i = 1; i <= n; ++i)
S = S + sqrt(i);
Lưu ý: Trong ngôn ngữ C/C++, vòng lặp for thực chất là một cách “bố trí lại các câu lệnh” của vòng
lặp while. Về bản chất thì 2 vòng lặp này là như nhau.
1 sqrt(<x>) Trả về giá trị căn bậc 2 của <x> (thuộc thư viện <cmath>)
2 abs(<x>) Trả về giá trị tuyệt đối của <x> (thuộc thư viện <cmath>)
3 max(<x>,<y>) Trả về giá trị lớn hơn trong 2 giá trị <x> và <y>
4 min(<x>,<y>) Trả về giá trị nhỏ hơn trong 2 giá trị <x> và <y>
Hàm trả về giá trị là được sử dụng khi bài toán con chỉ có đúng 1 output. Giá trị trả về của hàm chính là
giá trị output.
Cấu trúc hàm trả về giá trị:
- <danh sách input> được gọi là tham số hình thức của hàm (formal parameter).
- Câu lệnh return trả giá trị output cho hàm và kết thúc hàm. Giá trị output của hàm có thể được
tính thông qua một biểu thức có cùng kiểu dữ liệu với kiểu dữ liệu của output. Các lệnh nằm sau lời
gọi thực thi lệnh return sẽ bị bỏ qua.
- Vì có trả về giá trị nên khi gọi thực thi hàm, ta có thể lưu trữ giá trị trả về của hàm vào một biến có
cùng kiểu với dữ liệu được trả về.
Cú pháp gọi thực thi hàm có giá trị trả về: <tên biến> = <TênHàm>(<d/s input>);
Ví dụ 1.4.34:
Cho số nguyên dương 𝑛. Xây dựng số nguyên dương 𝑚 có các chữ số có thứ tự đảo ngược với thứ tự
các chữ số của 𝑛
Phân tích:
Input: 𝑁𝑢𝑚𝑏𝑒𝑟 ∈ ℤ+ .
Output: reverseNum ∈ ℤ+ là số đảo ngược của 𝑛.
Vì bài toán chỉ có 1 output là reverseNum nên ta sử dụng hàm có giá trị trả về:
#include <iostream>
using namespace std;
int main()
{
int n;
cin>>n;
int m = Reverse(n); //gọi thực thi hàm có giá trị trả về
cout<<m;
return 0;
}
Ví dụ 1.4.35:
Cho số nguyên 𝑛. Lập trình kiểm tra 𝑛 có phải là số nguyên tố và thông báo YES nếu 𝑛 là số nguyên
tố, ngược lại thông báo NO.
Phân tích:
Input: 𝑛 ∈ ℤ+ .
Output: True hoặc False tương ứng 𝑛 là số nguyên tố hoặc không là số nguyên tố.
Vì bài toán chỉ có 1 output là nên sử dụng hàm có giá trị trả về:
#include <iostream>
using namespace std;
bool IsPrime(int n)
{
if (n < 2)
return false;
int m = sqrt(n);
for (int i = 2; i <= m; ++i)
if (n % i == 0)
return false;
return true;
}
int main()
{
int n;
cin>>n;
if (IsPrime(n)) //gọi thực thi hàm có giá trị trả về
cout<<"YES";
else cout<<"NO";
return 0;
}
Hàm không trả về giá trị được sử dụng khi bài toán con không cho output hoặc cho nhiều output (từ 2
output trở lên).
Cấu trúc hàm không trả về giá trị
void TênHàm(<kiểu DL> <d/s input>, <kiểu DL> &<d/s output>)
{
<Thân hàm>;
}
- <d/s input> và <d/s output> được gọi là tham số hình thức của hàm.
- <d/s output> chỉ được khai báo hình thức khi số lượng <output> là cố định cho mọi trường hợp.
Trường hợp bài toán không có <output> hoặc có số lượng <output> không cố định thì <d/s
output> sẽ được lược bỏ đi.
- Trước mỗi <output> trong <d/s output> hình thức đều phải thêm dấu &.
- void là một kiểu dữ liệu đặc biệt cho biết hàm không trả về giá trị.
Cú pháp gọi thực thi hàm không trả về giá trị: <TênHàm>(<d/s input>, [<d/s output>]);
Ví dụ 1.4.36:
Cho số nguyên dương ℎ. Lập trình vẽ tam giác vuông cân có độ lớn cạnh góc vuông là ℎ bằng các kí
tự '*' có dạng như hình minh họa với ℎ = 4:
*
* *
* * *
* * * *
Phân tích:
Input: ℎ ∈ ℤ+
Output: không có.
Do bài toán không có output nên sử dụng hàm không trả về giá trị như sau:
#include <iostream>
using namespace std;
void DrawTriangle(int h)
{
for (int row = 1; row <= h; ++row)
{
for (int i = 1; i <= row; ++i)
cout<<"* ";
cout<<"\n";
}
}
Ví dụ 1.4.37:
Cho số nguyên dương 𝑛. Liệt kê tất cả ước dương của 𝑛.
Phân tích:
Input: 𝑛 ∈ ℤ+
Output: tất cả ước dương của 𝑛.
Do số lượng output của bài toán là không cố định nên sử dụng hàm không trả về giá trị như sau:
#include <iostream>
using namespace std;
void ListDivisors(int n)
{
for (int i = 1; i <= n; ++i)
if (n % i == 0)
cout<<i<<" ";
}
int main()
{
int n;
cin>>n;
ListDivisors(n);
return 0;
}
Ví dụ 1.4.38:
Cho số nguyên dương 𝑛. Đếm số chữ số và tính tổng các chữ số của 𝑛.
Phân tích
Bài toán có 2 output là số chữ số và tổng các chữ số của 𝑛 nên hàm xử lý là hàm không có giá trị trả
về (Solve).
#include <iostream>
Phân tích
Bài toán Solve (đếm và tính
tổng các chữ số của 𝑛)
void Solve(int n, int &sum, int &cnt) - Input: 𝑛 ∈ ℤ+
{ - Output: 𝑠𝑢𝑚, 𝑐𝑛𝑡 ∈ ℤ+
sum = cnt = 0;
while (n > 0)
{
sum = sum + n % 10;
++cnt;
n = n / 10;
}
}
int main()
{
int n, s, k;
cin>>n; Gọi thực thi hàm:
Mẹo
Solve(n, s, k); - s nhận giá trị của sum
cout<<s<<" "<<k; - k nhận giá trị của cnt
return 0;
}
Phạm vi của biến cho biết tầm hoạt động của biến trong một chương trình, nghĩa là phần nào của chương
trình thì được hay không được phép truy xuất đến một biến nào đó. Phạm vi của biến được chia thành 2
dạng chính: biến cục bộ (local variable) và biến toàn cục (global variable).
Biến cục bộ
Biến cục bộ là biến được khai báo bên trong hàm hoặc bên trong một khối lệnh (bên trong cặp dấu ngoặc
giới hạn {}). Biến cục bộ có phạm vi hoạt động chỉ bên trong thân hàm hoặc bên trong khối lệnh được
khai báo. Do đó các hàm hoặc các khối lệnh khác nhau có thể đặt trùng tên biến cục bộ.
Biến cục bộ nếu không được khởi tạo giá trị thì sẽ mang giá trị bất kỳ (gọi là giá trị rác). Bộ nhớ dành cho
biến cục bộ sẽ tự giải phóng khi ra khỏi phạm vi của hàm hoặc khối lệnh được khai báo.
Tham số hình thức của hàm đóng vai trò tương tự biến cục bộ của hàm đó.
Xét chương trình trong ví dụ 1.4.34:
- Biến reverseNum là biến cục bộ của hàm Reverse nên có phạm vi hoạt động chỉ bên trong hàm
này.
- Biến lastDigit là biến cục bộ trong vòng lặp while (n > 0) nên có phạm vi hoạt động chỉ bên
trong vòng lặp này.
- Biến n, m là biến cục bộ của hàm main nên có phạm vi hoạt động chỉ bên trong hàm main.
Biến toàn cục là biến được khai báo ở ngoài tất cả hàm trong chương trình và có phạm vi hoạt động từ vị
trí khai báo đến cuối chương trình. Bộ nhớ dành cho biến toàn cục chỉ được giải phóng khi kết thúc
chương trình.
Biến toàn cục nếu không được khởi tạo thì sẽ được tự động khởi tạo giá trị 0.
Trường hợp biến toàn cục trùng tên với biến cục bộ hoặc tham số của hàm, thì các biến đóng vai trò cục
bộ sẽ được ưu tiên trong phạm vi cục bộ.
Ví dụ 1.4.39:
Cho số nguyên dương 𝑛. Tìm số đảo ngược của 𝑛. (xét lại ví dụ 1.4.34)
Chương trình trong ví dụ 1.4.34 có thể được viết lại như sau:
#include <iostream>
using namespace std;
int main()
{
cin>>Number; //Number là biến toàn cục
reverseNum = Reverse(Number); //reverseNum là biến toàn cục
cout<<reverseNum;
return 0;
}
- Biến Number, reverseNum toàn cục có phạm vi hoạt động trong toàn bộ chương trình. Tuy nhiên,
hàm Reverse cũng có tham số Number và biến cục bộ reverseNum trùng tên với 2 biến toàn cục.
Trong trường hợp này, Number và reverseNum cục bộ sẽ được ưu tiên bên trong phạm vi hàm
Reverse.
int a = 5, b = 12, c;
int main()
{
c = Subtract(b, a);
cout<<"Global value: "<<a<<" "<<b<<" "<<c<<"\n";
return 0;
}
Kết quả output như sau
Local value: 12 5 7
Global value: 5 12 7
Lưu ý: Nên hạn chế sử dụng biến toàn cục vì có khả năng không kiểm soát được các hàm truy xuất
giá trị của biến toàn cục.
Ví dụ 1.4.41:
Cho số nguyên dương 𝑛. Liệt kê các số nguyên tố không vượt quá 𝑛. Mỗi số trên một dòng.
Ví dụ này minh họa trường hợp sử dụng biến toàn cục gây lỗi lặp vô hạn
#include <iostream>
#include <cmath>
int i, n;
bool IsPrime(int n)
{
if (n < 2) return false;
int m = sqrt(n);
for (i = 2; i <= m; ++i)
void PrimeList(int n)
{
for (i = 1; i <= n; ++i)
if (IsPrime(i))
cout<<i<<"\n";
}
int main()
{
cin>>n;
PrimeList(n);
return 0;
}
- Chương trình trên bị lỗi lặp vô hạn do xung đột giá trị biến đếm i của hai vòng lặp for trong hàm
IsPrime và hàm PrimeList. Lỗi trên được khắc phục bằng cách khai báo i là biến cục bộ của hai
hàm này.
BÀI 2
Viết chương trình nhập vào 3 số thực dương 𝑎, 𝑏, 𝑐. Cho biết 𝑎, 𝑏, 𝑐 có là 3 cạnh của 1 tam giác hay không.
Nếu có hãy xuất ra màn hình thông báo đó là tam giác loại gì trong các loại sau: đều, cân, vuông, vuông
cân, nhọn, tù.
Dữ liệu: Nhập vào 3 số thực 𝑎, 𝑏, 𝑐, (0 < 𝑎, 𝑏, 𝑐 ≤ 106 ).
Kết quả: Xuất ra thông báo KHONG PHAI TAM GIAC nếu 𝑎, 𝑏, 𝑐 không lập thành 3 cạnh của một tam
giác hoặc một trong các thông báo sau: TAM GIAC DEU, TAM GIAC CAN, TAM GIAC VUONG, TAM GIAC
VUONG CAN, TAM GIAC NHON, TAM GIAC TU.
Ví dụ:
INPUT OUTPUT
3 5 10 KHONG PHAI TAM GIAC
6 6 6 TAM GIAC DEU
BÀI 3
Viết chương trình nhập vào 2 số nguyên 𝑎, 𝑏 là số tuổi hiện tại của em và của anh. Cho biết sau bao nhiêu
năm nữa hay cách hiện tại bao nhiêu năm trước thì tuổi của anh gấp đôi tuổi của em.
Dữ liệu: Nhập vào 2 số nguyên 𝑎, 𝑏(0 < 𝑎 < 𝑏 ≤ 106 ).
Kết quả: Xuất ra thông báo sau x nam nua hoặc cach day x nam. Trong đó x là số năm cách năm
hiện tại mà tuổi anh gấp đôi tuổi em.
Ví dụ:
INPUT OUTPUT
5 8 cach day 2 nam
5 12 sau 2 nam nua
BÀI 4
Hình chữ nhật được gọi là che phủ hoàn toàn hình tròn nếu ta có thể đặt hình chữ nhật lên hình tròn trong
một mặt phẳng sao cho không có phần nào của hình tròn được nhìn thấy. Hãy kiểm tra hình chữ nhật có
thể che phủ hoàn toàn hình tròn hay không.
Dữ liệu: Nhập vào 3 số thực 𝑎, 𝑏, 𝑅(0 < 𝑎, 𝑏, 𝑅 ≤ 106 ).
Kết quả: Xuất ra thông báo YES hoặc NO tương ứng câu trả lời mảnh gỗ hình chữ nhật có kích thước
𝑎 × 𝑏 có thể che phủ được hoàn toàn một mảnh gỗ hình tròn bán kính 𝑅.
Ví dụ:
BÀI 5
Một hình chữ nhật che phủ hoàn toàn được hình chữ nhật khác nếu ta có thể đặt hình này lên hình còn lại
sao cho không có phần nào của hình còn lại được nhìn thấy. Hãy kiểm tra hình chữ nhật thứ nhất có thể
che phủ hoàn toàn hình chữ nhật thứ hai hay không.
Dữ liệu: Nhập vào 4 số nguyên 𝑎1 , 𝑏1 , 𝑎2 , 𝑏2 (0 < 𝑎1 , 𝑏1 , 𝑎2 , 𝑏2 ≤ 106 ).
Kết quả: Xuất ra thông báo YES hoặc NO tương ứng câu trả lời hình chữ nhật kích thước là 𝑎1 × 𝑏1 có
che phủ hoàn toàn được hình chữ nhật kích thước 𝑎2 × 𝑏2 .
Ví dụ:
INPUT OUTPUT
4 5 3 6 NO
5 10 2 2 YES
BÀI 6
Một tiệm bánh bán 2 loại bánh hình tròn có chất lượng và độ dày như nhau nhưng khác nhau về bán kính.
Loại thứ nhất có bán kính 𝑟1 và giá tiền 𝑐1 ; loại thứ hai có bán kính 𝑟2 và giá tiền 𝑐2 . Cho biết mua bánh
nào thì có lợi hơn theo nghĩa: cùng số tiền nhưng mua được lượng bánh nhiều hơn.
Dữ liệu: Nhập vào 4 số nguyên 𝑟1 , 𝑐1 , 𝑟2 , 𝑐2 (0 < 𝑟1 , 𝑐1 , 𝑟2 , 𝑐2 ≤ 106 ).
Kết quả: Xuất ra số nguyên 1 hay 2 cho biết loại bánh có lợi hơn.
Ví dụ:
INPUT OUTPUT
10 10 50 20 2
BÀI 7
Một tiệm bánh bán 2 loại bánh: loại thứ nhất có giá 𝑎 đồng/cái, loại thứ hai có giá 𝑏 đồng/cái. Một người
sử dụng toàn bộ số tiền 𝑐 để mua 2 loại bánh trên (hoặc chỉ mua 1 loại bánh mua cả 2 loại bánh). Cho biết
tất cả những cách mua bánh với số tiền đúng bằng 𝑐 đồng.
Dữ liệu: Nhập vào 3 số nguyên 𝑎, 𝑏, 𝑐(0 < 𝑎, 𝑏 ≤ 106 ; 0 < 𝑐 ≤ 109 ).
Kết quả: Xuất ra tất cả cách mua 2 loại bánh để số tiền phải trả đúng bằng 𝑐, mỗi cách trên một dòng.
Với mỗi cách mua xuất theo định dạng 𝑥 𝑦(𝑥, 𝑦 ≥ 0) – tương ứng với số lượng bánh từng loại. Kết quả
xuất tăng dần theo 𝑥.
Ví dụ:
INPUT OUTPUT
2 3 10 2 2
5 0
BÀI 9
Một cây con có chiều cao ban đầu là 𝑎. Nếu được chăm sóc thích hợp, mỗi ngày chiều cao của cây tăng
thêm 𝑏%. Hãy tìm chiều cao của cây sau 𝑛 ngày chăm sóc.
Dữ liệu: Nhập vào số nguyên 𝑎(1 ≤ 𝑎 ≤ 103 ), số thực 𝑏(1 ≤ 𝑏 ≤ 100) và số nguyên 𝑛(1 ≤ 𝑛 ≤ 105 ).
Kết quả: Xuất ra số thực là chiều cao của cây sau 𝑛 ngày chăm sóc. Kết quả được làm tròn 3 chữ số thập
phân.
Ví dụ:
INPUT OUTPUT
5 10 10 12.969
BÀI 10
Cho số nguyên dương 𝑛. Hãy liệt kê 𝑛 số nguyên tố đầu tiên ra màn hình.
Dữ liệu: Nhập vào số nguyên 𝑛(1 ≤ 𝑛 ≤ 104 ).
Kết quả: Xuất ra các số nguyên tố theo thứ tự tăng dần, mỗi số nằm trên một dòng.
Ví dụ:
INPUT OUTPUT
5 2
3
5
7
11
Lưu ý:
Các bài tập sau đây có dữ liệu được nhập xuất từ file văn bản để có thể chấm tự động. Cách đặt tên file
bài làm (*.cpp), file input (*.inp) và output (*.out) được quy định trong mỗi bài. Trong đó 3 tập tin này ở
mỗi bài (*.cpp, *.inp, *.out) được đặt cùng tên nhau, chỉ khác nhau phần mở rộng.
Các câu lệnh dùng để nhập xuất file (trong thư viện <cstdio>)
int main()
{
freopen("input.txt", "r", stdin); //mở file input.txt để đọc
freopen("output.txt", "w", stdout); //mở file output.txt để ghi
...
return 0;
}
Sau khi gọi câu lệnh freopen thì các thao tác đọc dữ liệu từ file hay ghi kết quả ra file được sử dụng
bằng các lệnh cin và cout như thông thường.
GAME THỦ
Một game thủ đang tích cực cày một game mới. Nhiệm vụ của game thủ là tiêu diệt các quái vật có level
tăng dần từ level 1 trở đi. Mỗi lần tiêu diệt được quái vật ở level 𝑘 thì game thủ được 𝑘 điểm. Nghĩa là
tiêu diệt quái vật ở level 1 được 1 điểm, tiêu diệt quái vật ở level 2 được 2 điểm, … và level phải được
bắt đầu từ 1 trở đi. Game thủ hiện cần có 𝑛 điểm để mua 1 bảo vật. Cho biết game thủ cần phải tiêu diệt
xong quái vật ở level bao nhiêu.
Yêu cầu: Cho số nguyên dương 𝑛(𝑛 ≤ 109 ). Cho biết level mà game thủ cần phải đạt đến để đủ 𝑛 điểm
thưởng.
Dữ liệu: Vào từ tập tin văn bản GAMER.INP số nguyên 𝑛(1 ≤ 𝑛 < 109 ).
Kết quả: Ghi ra tập tin văn bản GAMER.OUT số nguyên tương ứng với level mà game thủ cần đạt.
SƠN TƯỜNG
Tí đang trang trí bằng cách quét sơn lên một dãy gồm 𝑛 ô vuông nằm liên tiếp nhau như hình minh họa,
các ô được đánh số từ 1 đến 𝑛. Tí dự định sẽ sơn các ô vuông bằng 2 màu sơn đen và trắng như sau:
- Ban đầu tất cả ô vuông đều được sơn màu trắng.
- Tiếp theo những ô vuông có số thứ tự là ước của 𝑛 thì được sơn màu đen.
Tí nhận thấy rằng nếu làm như vậy, một số ô được quét 2 lần nước sơn, điều này gây lãng phí nên Tí cần
phải tính toán trước số lượng ô cần sơn từng màu cụ thể.
1 2 3 4 5 6 7 8
Yêu cầu: Cho số nguyên dương 𝑛. Hãy đếm số lượng ô được sơn trắng và số lượng ô được sơn đen.
Dữ liệu: Vào từ tập tin văn bản PAINTING.INP số nguyên 𝑛(1 ≤ 𝑛 ≤ 1014 ).
Kết quả: Ghi ra tập tin văn bản PAINTING.OUT 2 số nguyên 𝑥, 𝑦 tương ứng với số lượng ô cần được
sơn màu trắng và màu đen. Các số xuất trên một dòng và cách nhau 1 khoảng trắng.
Ví dụ:
PAINTING.INP PAINTING.OUT
8 4 4
DỊCH CHỮ SỐ
Một phép dịch phải số nguyên dương 𝑛 sẽ dịch chuyển các chữ số của 𝑛 sang phải một vị trí, chữ số hàng
đơn vị sẽ được chuyển lên hàng đầu tiên (hàng trái nhất). Chẳng hạn với 𝑛 = 2016 thì sau một phép dịch
SỐ HẠNH PHÚC
Số nguyên dương 𝑥 được gọi là hạnh phúc nếu 𝑥 có 2 × 𝑘 chữ số và tổng của 𝑘 chữ số đầu bằng tổng của
𝑘 chữ số cuối. Chẳng hạn số 135027 là số hạnh phúc vì 1 + 3 + 5 = 0 + 2 + 7.
Yêu cầu: Cho số nguyên dương 𝑛. Liệt kê tất cả số hạnh phúc không vượt quá 𝑛.
Dữ liệu: Vào từ tập tin văn bản HNUM.INP số nguyên dương 𝑛(𝑛 ≤ 107 ).
Kết quả: Ghi ra tập tin văn bản HNUM.OUT các số hạnh phúc có giá trị không vượt quá 𝑛, mỗi số trên
một dòng và theo thứ tự tăng dần. Nếu không tìm được số hạnh phúc nào thì xuất −1.
Ví dụ:
HNUM.INP HNUM.OUT
100 11
22
33
44
55
66
77
88
99
Yêu cầu: Cho 2 số nguyên dương 𝑛, 𝑚. Cho biết số hình vuông có trong lưới.
Dữ liệu: Vào từ tập tin văn bản CNTSQR.INP 2 số nguyên 𝑛, 𝑚(1 ≤ 𝑛, 𝑚 ≤ 106 ).
MÁY ATM
Hiện trong máy ATM chỉ còn các tờ bạc mệnh giá 5 đồng, 10 đồng và 20 đồng với số lượng tương ứng
𝑎, 𝑏, 𝑐 (1 ≤ 𝑎, 𝑏, 𝑐 ≤ 105 ) tờ. Một người cần rút một số tiền 𝑛 đồng (1 ≤ 𝑛 ≤ 105 ). Cho biết máy ATM
có bao nhiêu cách khác nhau để trả đủ 𝑛 đồng cho khách hàng bằng số tiền hiện có trong máy.
Yêu cầu: Cho 4 số nguyên dương 𝑎, 𝑏, 𝑐, 𝑛. Cho biết số cách trả tiền khác nhau.
Dữ liệu: Vào từ tập tin văn bản ATM.INP chứa 4 số nguyên dương 𝑎, 𝑏, 𝑐, 𝑛.
Kết quả: Ghi ra tập tin văn bản ATM.OUT số cách trả tiền tìm được.
Ví dụ:
ATM.INP ATM.OUT
5 3 2 50 6
Giải thích: có 6 cách trả tiền: (0,1,2), (0,3,1), (2,0,2), (2,2,1), (4,1,1), (4,3,0)
KHUYẾN MÃI
Công ty nước ngọt vừa đưa ra chương trình khuyến mãi vô cùng hấp dẫn cho các fan của loại nước uống
này. Nắp chai nước ngọt sau khi khui ra sẽ được dùng để tích lũy đổi chai mới cùng loại, với 𝑘 nắp chai
sẽ được đổi 1 chai nước ngọt cùng loại và nắp của chai này cũng có giá trị đổi tiếp tục. Một người sau
một thời gian đã tích lũy được 𝑛 nắp chai. Cho biết với số nắp chai hiện tại thì trong thời gian tới người
này có thể uống được bao nhiêu chai nước ngọt miễn phí.
Yêu cầu: Cho 2 số nguyên dương 𝑛, 𝑘. Tính số chai nước ngọt có thể nhận miễn phí.
Dữ liệu: Vào từ tập tin văn bản PROMOTION.INP chứa 2 số nguyên 𝑛, 𝑘(2 ≤ 𝑘 ≤ 109 ; 1 ≤ 𝑛 ≤ 109 ).
Kết quả: Ghi ra tập tin văn bản PROMOTION.OUT số lượng chai nước ngọt nhận miễn phí.
Ví dụ:
PROMOTION.INP PROMOTION.OUT
9 5 2
Giải thích: Với 9 nắp, người này lấy 5 nắp để đổi lấy 1 chai nước ngọt. Sau khi sử dụng xong chai vừa
đổi được thì nắp của chai này cộng với 4 nắp còn dư thì đổi được thêm 1 chai nữa.
SỐ HỮU NGHỊ
Hai số tự nhiên khác nhau được gọi là hữu nghị nếu tổng các ước (không kể 1 và chính nó) của số này
bằng giá trị của số kia và ngược lại. Chẳng hạn 48 và 75 là cặp số hữu nghị vì 75 = 2 + 3 + 4 + 6 +
8 + 12 + 16 + 24 và 48 = 3 + 5 + 15 + 25.
Yêu cầu: Cho số nguyên dương 𝑛. Liệt kê tất cả cặp số hữu nghị không vượt quá 𝑛.
Dữ liệu: Vào từ tập tin văn bản FNUM.INP số nguyên dương 𝑛(48 ≤ 𝑛 ≤ 105 ).
Kết quả: Ghi ra tập tin văn bản FNUM.OUT tất cả cặp số hữu nghị dạng 𝑥, 𝑦(𝑥 < 𝑦 ≤ 𝑛). Mỗi cặp số
trên một dòng. Các cặp số được liệt kê tăng dần theo 𝑥. Nếu không tìm được cặp số hữu nghị nào thì xuất
−1.
Ví dụ:
FNUM.INP FNUM.OUT
100 48 75
Cột dữ liệu về số lượng học sinh của từng lớp trong bảng trên tạo thành mảng một chiều được minh họa
như hình bên dưới, danh sách dữ liệu về số lượng học sinh được đặt tên Count.
Count 29 38 38 35 37 32 31 33 28 32
0 1 2 3 4 5 6 7 8 9
Mỗi ô trong dãy biểu diễn một phần tử của mảng. Phần tử thứ 0 có giá trị 29 cho biết số học sinh của lớp
thứ 0 là 29, tương tự phần tử thứ 1 có giá trị 38, …
Lưu ý: Mảng nên được khai báo toàn cục khi có kích thước lớn (cỡ 103 phần tử trở lên).
Ví dụ 2.2.1: Khai báo 2 mảng lưu giá trị 2 dãy số nguyên 𝑎0 , 𝑎1 , … , 𝑎𝑛−1 và 𝑏0 , 𝑏1 , … , 𝑏𝑚−1 với
(𝑛, 𝑚 ≤ 103 ).
int a[1000], b[1000];
Ví dụ 2.2.2: Khai báo mảng lưu trữ điểm trung bình học kì của các học sinh trong một lớp.
double GPA[100];
Ví dụ 2.2.3: Khai báo mảng số nguyên 𝑎 có kích thước 1000 và khởi tạo tất cả phần tử của mảng có giá
trị 0.
int a[1000] = {0};
Lưu ý: Khi khai báo và khởi tạo như sau int a[1000] = {10}; thì chỉ có phần tử a[0] có giá trị
10, các phần tử còn lại có giá trị 0.
a[1] = 10;
cout<<GPA[1];
++b[0];
cout<<b[0];
Lưu ý: chỉ số phần tử của mảng phải là một giá trị nguyên thuộc phạm vi từ 0 đến <size>-1, nghĩa
là 0 chỉ_số <size>-1, với <size> là kích thước mảng.
#include <iostream>
int a[maxN], n;
void ReadData()
{
cin>>n;
for (int i = 0; i < n; ++i)
cin>>a[i];
}
int Solve()
{
int s = 0;
for (int i = 0; i < n; ++i)
if (a[i] % 2 == 0)
s = s + a[i];
return s;
}
int main()
{
Lưu ý: Nên chia các công việc xử lý thành các hàm, mỗi hàm thực hiện một tác vụ độc lập của thuật
toán.
void ReadData()
{
cin>>n>>m;
for (int i = 0; i < n; ++i)
cin>>a[i];
for (int i = 0; i < m; ++i)
cin>>b[i];
}
int main()
{
Lưu ý
- Nếu sử dụng mảng là tham số cho hàm thì khi khai báo không cần chỉ định kích thước. Khi mảng
là tham số của hàm thì mảng có vai trò là tham biến.
- Sử dụng mảng là tham số cho hàm giúp hàm có tính tái sử dụng trong trường hợp thực hiện các
thao tác tương tự trên các mảng khác nhau.
0 1 … 𝑚−1
1 Phần tử
mảng
…
𝑛−1
Dòng được đánh số thứ tự liên tiếp từ trên xuống và bắt đầu từ 0, cột được đánh thứ tự liên tiếp từ trái
qua phải và bắt đầu từ 0.
Mỗi phần tử của bảng tương đương một biến trong chương trình và được xác định thông qua chỉ số dòng,
cột của nó trong bảng.
Lưu ý: Mảng nhiều chiều có cú pháp khai báo tổng quát như sau
<kiểu dữ liệu> <tên_mảng>[<size_1>][<size_2>]…[<size_k>];
Trong đó size_1, size_2, …, size_k là kích thước của 𝑘 chiều tương ứng.
Ví dụ 2.3.2:
a[1][1] = 10;
cin>>a[i][j];
cout<<a[10][5];
int a[maxN+1][maxN+1], n, m;
Tăng kích thước thêm 1 vì
void ReadData()
các chỉ số tính từ 1 trở đi
void Solve()
{
int x = 1, y = 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (a[i][j] > a[x][y])
x = i, y = j;
Câu lệnh kép
cout<<x<<" "<<y;
}
int main()
{
ReadData();
Solve();
return 0;
}
Ví dụ 2.3.4: Cho bảng vuông các số nguyên kích thước 𝑛 × 𝑛(𝑛 ≤ 103 ). Cho biết các phần tử của bảng
có đối xứng qua đường chéo chính hay không. Đường chéo chính của bảng là đường chéo bắt đầu từ ô
(1,1) đến ô (𝑛, 𝑛).
Input: Dòng đầu chứa số nguyên 𝑛. Mỗi dòng trong 𝑛 dòng tiếp theo chứa dãy gồm 𝑛 số nguyên mô
tả các phần tử trên bảng.
Output: Thông báo YES tương ứng với câu trả lời có, ngược lại thông báo NO.
#include <iostream>
void ReadData()
{
cin>>n;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
cin>>a[i][j];
}
bool Solve()
return true;
}
int main()
{
ReadData();
if (Solve())
cout<<"YES";
else cout<<"NO";
return 0;
}
Ví dụ 2.4.3: Khai báo vector 𝑣3 các số nguyên có kích thước ban đầu là 100 và khởi tạo tất cả phần tử
của vector có giá trị -5.
vector<int> v3(100, -5);
Ví dụ 2.4.4: Khai báo vector 𝑣 các số nguyên long long có kích thước ban đầu là 105 và khởi tạo tất
cả phần tử của vector có giá trị 1.
vector<long long> v(100000, 1);
6 begin() Trả về con trỏ đến địa chỉ phần tử đầu tiên của vector
7 end() Trả về con trỏ đến địa chỉ sau phần tử cuối của vector
begin() end()
#include <iostream>
using namespace std;
#define maxN 1000000 //định nghĩa hằng số maxN có giá trị 10^6
int a[maxN], n, x;
void ReadData()
{
cin>>n>>x;
for (int i = 0; i < n; ++i)
cin>>a[i];
}
int Solve()
{
int cnt = 0;
for (int i = 0; i < n; ++i)
if (a[i] == x)
++cnt;
return cnt;
}
int main()
{
ReadData();
cout<<Solve();
return 0;
}
Ví dụ 2.4.6: Cho dãy gồm 𝑛 số nguyên có giá trị tuyệt đối không vượt quá 1012 . Đếm số phần tử của dãy
là số Palindrome.
Dữ liệu: Nhập từ bàn phím gồm 2 dòng
- Dòng đầu tiên chứa số nguyên dương 𝑛(𝑛 ≤ 105 ).
- Dòng thứ hai chứa dãy 𝑎0 , 𝑎1 , … , 𝑎𝑛−1 (|𝑎𝑖 | ≤ 1012 ).
Kết quả: Xuất ra màn hình một số nguyên là số phần tử thỏa Palindrome.
#include <iostream>
#include <vector> //thư viện chứa kiểu vector
using namespace std;
typedef long long ll;
void ReadData()
{
int n; ll x;
cin>>n;
for (int i = 0; i < n; ++i)
{
cin>>x;
v.push_back(x);
}
}
bool checkPALIND(ll n)
{
ll m1 = 0, m2 = n;
while (n > 0)
{
m1 = m1*10 + n%10;
n = n/10;
}
return (m1 == m2);
}
int Solve()
{
int cnt = 0;
for (int i = 0; i < v.size(); ++i)
if (checkPALIND(v[i])) ++cnt;
return cnt;
}
int main()
{
ReadData();
cout<<Solve();
return 0;
}
Ví dụ 2.4.7: Cho dãy gồm 𝑛(𝑛 ≤ 105 ) số nguyên có giá trị tuyệt đối không vượt quá 106 . Tìm số chính
phương lớn nhất và lớn nhì dãy.
Dữ liệu: Nhập từ bàn phím gồm 2 dòng
- Dòng đầu tiên chứa số nguyên dương 𝑛.
- Dòng thứ hai chứa dãy 𝑎0 , 𝑎1 , … , 𝑎𝑛−1 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi 2 số nguyên 𝑖, 𝑗(0 ≤ 𝑖, 𝑗 < 𝑛) tương ứng với vị trí của số chính phương lớn nhất và lớn
thứ nhì. Nếu không tìm được số chính phương ở vị trí nào thì ghi −1 ở vị trí tương ứng.
#include <iostream>
#include <vector>
#include <cmath> //thư viện chứa hàm sqrt
vector<int> a;
void ReadData()
{
int n;
cin>>n;
a.resize(n); //thay đổi kích thước của a từ 0 thành n
for (int i = 0; i < n; ++i)
cin>>a[i];
}
bool checkSQR(int n)
{
if (n < 0) return false;
int m = (int)sqrt(n); //ép kiểu: lấy phần nguyên của sqrt(n)
return (m*m == n);
}
int main()
{
ReadData();
int p1 = MaxSQR(-1); //vị trí số chính phương lớn nhất
int p2 = MaxSQR(p1); //vị trí số chính phương lớn nhì
cout<<p1<<" "<<p2;
return 0;
}
Ví dụ 2.4.8: Cho dãy số nguyên 𝑎0 , 𝑎1 , … , 𝑎𝑛−1 . Tìm vị trí của phần tử dương chẵn lớn nhất dãy. Nếu dãy
không tồn tại số dương chẵn thì ghi −1.
#include <iostream>
#include <vector>
#include <cstdio>
vector<int> a;
void ReadData()
{
int n;
cin>>n;
a.resize(n);
for (int i = 0; i < n; ++i)
cin>>a[i];
}
int Solve()
{
int pos = -1;
for (int i = 0; i < a.size(); ++i)
{
if (a[i] > 0 && a[i] % 2 == 0)
if (pos == -1 || a[i] > a[pos])
pos = i;
}
return pos;
}
int main()
{
freopen("input.txt", "r", stdin); //mở file input.txt để đọc
freopen("output.txt", "w", stdout); //mở file output.txt để ghi
ReadData();
int p = Solve();
cout<<p;
return 0;
}
Ví dụ 2.4.9: Cho dãy gồm 𝑛(𝑛 ≤ 105 ) số nguyên có giá trị tuyệt đối không vượt quá 106 . Hãy tìm đường
chạy dài nhất của dãy.
#include <iostream>
#include <vector>
#include <cstdio> //thư viện chứa hàm freopen
//mỗi vector lưu một đường chạy, khai báo mảng các vector
vector<int> a[maxN];
int m = 0; //m: số phần tử của mảng vector
void ReadData()
{
int n, x1 = (-1e6) - 10, x2; //1e6 = 10^6 = 1000000
cin>>n;
for (int i = 0; i < n; ++i)
{
cin>>x2;
if (x2 >= x1)
a[m].push_back(x2);
else a[++m].push_back(x2);
x1 = x2;
}
}
int MaxRuns()
{
int imax = 0;
for (int i = 1; i <= m; ++i)
{
if (a[i].size() > a[imax].size())
imax = i;
}
return imax;
}
1) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và số nguyên 𝑥(|𝑥| ≤ 106 ). Hãy đếm số lượng phần tử của dãy là
ước của 𝑥.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng chứa 2 số nguyên dương 𝑛, 𝑥(𝑛 ≤ 106 ; |𝑥| ≤ 106 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản số lượng phần tử của dãy là ước của 𝑥.
Ví dụ:
INPUT OUTPUT
7 20 4
5 3 -4 2 6 9 10
2) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Tìm độ lệch nhỏ nhất giữa các phần tử nằm cạnh nhau.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng đầu chứa số nguyên dương 𝑛(𝑛 ≤ 106 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản độ lệch nhỏ nhất tìm được.
Ví dụ:
INPUT OUTPUT
7 1
5 3 -4 2 6 9 10
3) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Hãy đếm số lượng phần tử của dãy là số nguyên tố.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng đầu chứa số nguyên dương 𝑛(𝑛 ≤ 104 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản một số nguyên là kết quả bài toán.
Ví dụ:
INPUT OUTPUT
7 3
5 3 -4 2 6 9 10
4) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Tìm giá trị phân biệt nhỏ nhất và nhỏ thứ nhì dãy.
5) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Tìm vị trí của phần tử nhỏ nhất và nhỏ thứ nhì dãy (hoặc nhỏ nhất
đồng hạng) của dãy.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng đầu chứa số nguyên dương 𝑛(𝑛 ≤ 106 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản vị trí của 2 phần tử cần tìm tương ứng. Nếu có nhiều kết quả thì ghi
các vị trí trái nhất.
Ví dụ:
INPUT OUTPUT
7 2 3
5 2 2 7 10 8 2
6) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Tìm vị trí của số nguyên tố lớn nhất dãy. Nếu dãy không tồn tại
số nguyên tố thì ghi −1. Nếu có nhiều số nguyên tố lớn nhất thì in ra vị trí nhỏ nhất.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng đầu chứa số nguyên dương 𝑛(𝑛 ≤ 104 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản vị trí của số nguyên tố lớn nhất dãy hoặc −1.
Ví dụ:
INPUT OUTPUT
7 4
5 2 2 7 10 8 2
7) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Một đường chạy có độ dài 𝑘(𝑘 ≥ 1) là một dãy con dài nhất gồm
các phần tử liên tiếp thỏa 𝑎𝑖 ≤ 𝑎𝑖+1 ≤ ⋯ ≤ 𝑎𝑖+𝑘−1 . Hãy xuất ra tất cả đường chạy của dãy, mỗi đường
chạy trên một dòng.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
8) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Một đường chạy có độ dài 𝑘(𝑘 ≥ 1) là một dãy con dài nhất gồm
các phần tử liên tiếp thỏa 𝑎𝑖 ≤ 𝑎𝑖+1 ≤ ⋯ ≤ 𝑎𝑖+𝑘−1 . Hãy tìm đường chạy dài nhất.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng đầu chứa số nguyên dương 𝑛(𝑛 ≤ 106 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản một số nguyên là độ dài đường chạy dài nhất.
Ví dụ:
INPUT OUTPUT
7 4
5 2 2 7 10 8 12
9) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Hãy tìm một dãy con dài nhất gồm các phần tử liên tiếp là số chính
phương. Số chính phương là số nguyên dương bằng bình phương của một số nguyên dương.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng đầu chứa số nguyên dương 𝑛(𝑛 ≤ 106 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản một số nguyên là độ dài của dãy con tìm được.
Ví dụ:
INPUT OUTPUT
7 3
5 2 25 9 1 28 16
10) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Một phép dịch trái sẽ dời phần tử nằm ở vị trí 𝑖 sang vị trí 𝑖 −
1(2 ≤ 𝑖 ≤ 𝑛) và phần tử nằm ở vị trí đầu (vị trí 1) sẽ được dời đến cuối dãy (vị trí 𝑛). Ví dụ dãy 1, 2, 3, 4, 5
sau 1 phép dịch trái sẽ trở thành 2, 3, 4, 5, 1; nếu dịch trái 1 lần nữa sẽ trở thành 3, 4, 5, 1, 2. Hãy cho biết
dãy kết quả sau khi thực hiện 𝑚 phép dịch trái đối với dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 .
Yêu cầu: Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và số nguyên 𝑚. Hãy cho biết trạng thái của dãy khi thực
hiện 𝑚 phép dịch trái.
11) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Hãy chỉ ra một dãy con dài nhất gồm các phần tử nằm liên tiếp là
dãy đối xứng.
Dữ liệu: Vào từ tập tin văn bản gồm 2 dòng
- Dòng đầu chứa số nguyên dương 𝑛(𝑛 ≤ 100).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản 1 số nguyên là chiều dài của dãy con thỏa điều kiện.
Ví dụ:
INPUT OUTPUT
7 3
5 2 25 2 28 28 16
Thời gian thực thi là yếu tố quan trọng giúp đánh giá tính hiệu quả một thuật toán. Thời gian thực thi của
thuật toán phụ thuộc vào các yếu tố sau:
- Độ lớn dữ liệu (hay còn gọi là kích thước bài toán)
Ví dụ 3.1.1: Tìm kiếm phần tử có giá trị 𝑥 trong dãy số có 𝑛 phần tử. Với 𝑛 = 10 thì thời gian thực
thi sẽ nhanh hơn với 𝑛 = 106 . Vậy kích thước của bài toán là 𝑛.
Ví dụ 3.1.2: Tính giá trị của 𝑎𝑛 . Khi 𝑛 càng lớn thì thời gian thực hiện phép tính càng lớn. Vậy kích
thước của bài toán là 𝑛.
- Trạng thái dữ liệu
Ví dụ 3.1.3: Nếu ta cần sắp xếp tăng dần một dãy số có 𝑛 phần tử mà dãy này đã có thứ tự tăng dần
rồi thì thời gian thực hiện sẽ nhanh hơn so với sắp xếp dãy chưa có thứ tự.
- Tốc độ máy tính
- Ngôn ngữ lập trình
- Kỹ năng cài đặt
Các yếu tố trên ảnh hưởng một cách chủ quan đến tính hiệu quả của thuật toán. Do đó cần chọn một yếu
tố mang tính khách quan hơn đó là số thao tác sơ cấp mà thuật toán thực hiện. Các thao tác sơ cấp bao
gồm:
- Các phép toán số học
- Phép gán
STT 𝒏 𝐥𝐨𝐠 𝟐 𝒏
1 10 3.322
2 103 9.966
3 106 19.931
4 109 29.897
5 1018 59.795
int S = 0, P = 1;
for (int i = 1; i <= n; ++i)
{
S = S + i;
Đoạn chương trình trên lặp 𝑛 lần, mỗi lần lặp thực hiện đúng 2 thao tác sơ cấp nên độ phức tạp 𝑇(𝑛) =
𝑂(2 × 𝑛) = 𝑂(𝑛).
Ví dụ 3.3.2: Xét đoạn chương trình đếm số lượng cặp phần tử khác nhau có tổng đúng bằng 𝑥.
int cnt = 0;
for (int i = 0; i < n; ++i)
{
for (int j = i+1; j < n; ++j)
if (a[i] + a[j] == x)
++cnt;
}
Số lần kiểm tra điều kiện if (a[i] + a[j] == x) bằng số lần lặp của 2 vòng lặp
𝑛(𝑛 − 1)
1 + 2 + ⋯ + (𝑛 − 1) =
2
Số lần thực thi câu lệnh ++cnt tùy thuộc vào biểu thức điều kiện (a[i] + a[j] == x). Trường hợp
tốt nhất là biểu thức điều kiện luôn sai, số lần thực thi câu lệnh là 0. Trường hợp xấu nhất là câu lệnh luôn
thực thi và bằng đúng số lần kiểm tra điều kiện. Vậy độ phức tạp của đoạn chương trình là
𝑛(𝑛 − 1)
𝑂( ) = 𝑂(𝑛(𝑛 − 1)) = 𝑂(𝑛2 − 𝑛)
2
Lưu ý
- Đối với các hàm số logarit ta có: log 2 𝑛 = log 2 10 × log10 𝑛. Áp dụng quy tắc bỏ hệ số ta có:
𝑂(log 2 𝑛) = 𝑂(log 2 10 × log10 𝑛) = 𝑂(log10 𝑛). Do vậy khi biểu diễn độ phức tạp thuật toán là
một hàm số logarit, người ta thường bỏ đi cơ số và sử dụng kí hiệu độ phức tạp 𝑂(log 𝑛) để thay
cho 𝑂(log 2 𝑛) hoặc 𝑂(log10 𝑛).
Ví dụ 3.3.5: Cho dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 . Xét đoạn chương trình đếm số lượng phần tử của dãy là số nguyên tố.
bool checkPrime(int n)
{
if (n < 2) return false;
int m = (int)sqrt(n);
for (int i = 2; i <= m; ++i)
if (n % i == 0)
return false;
return true;
}
int countPrime(int n)
{
int cnt = 0;
for (int i = 1; i <= n; ++i)
if (checkPrime(a[i]))
++cnt;
return cnt;
}
Độ phức tạp của hàm countPrime: 𝑇(𝑛) = 𝑂(𝑛 × 𝑇2 (|𝑎𝑖 |)) = 𝑂(𝑛 × √|𝑎𝑖 |).
Thuật toán 2:
- Nhận xét: nếu 𝑎 là ước dương của 𝑛 thì luôn tồn tại 𝑏 cũng là ước dương của 𝑛 sao cho 𝑎 × 𝑏 = 𝑛.
- Giả sử 𝑎 ≤ 𝑏 ⟹ 𝑎2 ≤ 𝑎 × 𝑏 = 𝑛 ⟹ 1 ≤ 𝑎 ≤ √𝑛.
Đoạn chương trình thực hiện
int countDivisors(int n)
{
int m = (int)sqrt(n), cnt = 0;
for (int a = 1; a <= m; ++a)
if (n % a == 0)
cnt = cnt + 2; //tìm được 2 ước là a và b = n/a
if (m*m == n) –-cnt;
return cnt;
}
Độ phức tạp của thuật toán 2 là 𝑂(√𝑛).
return smax;
}
Thuật toán sử dụng 3 vòng lặp lồng nhau nên có độ phức tạp 𝑂(𝑛3 ).
void InterchangeSort(int n)
{
for (int i = 1; i < n; ++i)
for (int j = i+1; j <= n; ++j)
if (a[i] >= a[j]) swap(a[i], a[j]);
}
<𝑥 =𝑥 >𝑥
…
𝑙𝑜 ℎ𝑖
Hình 4.1.3.2. Sử dụng con trỏ 𝑙 xuất phát từ 𝑙𝑜 và con trỏ 𝑟 xuất phát từ ℎ𝑖
Đoạn chương trình phân hoạch dãy thành 2 đoạn con theo chốt 𝑥
while (l <= r)
{
while (a[l] < x) ++l;
while (a[r] > x) –-r;
if (a[l] <= a[r])
{
swap(a[l], a[r]);
++l, --r;
}
}
Sau đoạn lệnh trên, dãy được phân hoạch thành 2 đoạn con (như hình minh họa 4.1.3.3):
- Đoạn trái: gồm các phần tử từ vị trí 𝑙𝑜 đến vị trí 𝑟 và có giá trị < 𝑥
- Đoạn phải: gồm các phần tử từ vị trí 𝑙 đến vị trí ℎ𝑖 và có giá trị > 𝑥
𝑟 𝑙
…
𝑙𝑜 Hình 4.1.3.3. Dãy sau khi phân hoạch ℎ𝑖
Các phần tử trong mỗi đoạn con này chưa đảm bảo có thứ tự, vì vậy ta tiếp tục phân hoạch từng đoạn con
này cho đến khi không thể phân hoạch được nữa (đoạn chỉ có 1 phần tử).
Danh sách sau khi được sắp xếp có thứ tự tăng dần theo 𝑥, cùng 𝑥 thì sắp tăng dần theo 𝑦:
i 1 2 3 4 5 6 7 8 9 10
x 3 3 3 4 4 5 6 8 8 12
y 2 7 9 2 7 10 4 3 6 1
Ta thấy rằng việc sắp thứ tự danh sách phụ thuộc vào thao tác so sánh 2 phần tử để xác định thứ tự của
chúng (phần tử nhỏ hơn đứng trước). Với cặp phần tử (𝑎𝑖 , 𝑎𝑗 ), (𝑎𝑖 < 𝑎𝑗 ) nếu thỏa điều kiện sau:
struct pii
{
int x, y;
} a[maxN];
Đoạn chương trình minh họa sắp xếp danh sách 2 khóa bằng phương pháp đổi chỗ trực tiếp:
void InterchangeSort(int n)
{
for (int i = 1; i < n; ++i)
for (int j = i+1; j <= n; ++j)
if (compare(a[j], a[i])) swap(a[i], a[j]);
}
Đoạn chương trình minh họa sắp xếp danh sách 2 khóa bằng phương pháp quick sort:
void quicksort(int lo, int hi)
{
int l = lo, r = hi;
pii x = a[l + rand() % (r-l+1)];
while (l <= r)
{
while (compare(a[l], x)) ++l;
while (compare(x, a[r])) –-r;
if (l <= r)
swap(a[l++], a[r--]);
}
if (lo < r) quicksort(lo, r);
if (l < hi) quicksort(l, hi);
}
Ý tưởng: dựa vào tính có thứ tự của không gian tìm kiếm để loại bỏ đi một phần không gian không tồn tại
phần tử cần tìm kiếm. Phần không gian tìm kiếm bị loại bỏ bằng một nửa so với không gian tìm kiếm ban
đầu.
Thuật toán: tìm vị trí của phần tử có giá trị 𝑥 trong dãy 𝑎1 ≤ 𝑎2 ≤ ⋯ ≤ 𝑎𝑛 . Tổng quát, ta cần tìm vị trí
của 𝑥 trong dãy 𝑎𝑙𝑜 ≤ 𝑎𝑙𝑜+1 ≤ ⋯ ≤ 𝑎ℎ𝑖
- Bước 1: Nếu dãy không có phần tử (𝑙𝑜 > ℎ𝑖) thì dừng thất bại.
- Bước 2: Đặt 𝑚 = (𝑙𝑜 + ℎ𝑖)/2.
- Bước 3: Kiểm tra 𝑥 = 𝑎𝑚 .
Nếu đúng thì dừng thành công.
Ngược lại đến Bước 4.
- Bước 4: Kiểm tra 𝑥 < 𝑎𝑚 .
Nếu đúng thì đoạn [𝑚, ℎ𝑖] không chứa 𝑥. Vùng tìm kiếm là [𝑙𝑜, 𝑚 − 1]. Quay về Bước 1.
2 3 5 8 9 10 13 16 20
1 2 3 4 5 6 7 8 9
+ Do 𝑥 < 𝑎[5] nên 𝑥 không thể xuất hiện trong đoạn [5. .9]. Vùng tìm kiếm là [1. .4].
2 3 5 8 9 10 13 16 20
1 2 3 4 5 6 7 8 9
- Lần thứ 2: so sánh 𝑥 = 5 với phần tử giữa đoạn [1. .4] (vị trí 2).
2 3 5 8 9 10 13 16 20
1 2 3 4 5 6 7 8 9
+ Do 𝑥 > 𝑎[2] nên 𝑥 không thể xuất hiện trong đoạn [1. .2]. Vùng tìm kiếm là [3. .4].
2 3 5 8 9 10 13 16 20
1 2 3 4 5 6 7 8 9
- Lần thứ 3: so sách 𝑥 = 5 với phần tử giữa đoạn [3. .4] (vị trí 3).
2 3 5 8 9 10 13 16 20
1 2 3 4 5 6 7 8 9
Phương pháp tìm kiếm nhị phân còn được áp dụng để tìm giá trị lớn nhất hoặc nhỏ nhất trong đoạn giá
trị [𝑙𝑜. . ℎ𝑖] thỏa điều kiện nào đó. Một cách hình thức, bài toán có thể được mô hình hóa như sau: tìm giá
trị 𝑥 lớn nhất hoặc nhỏ nhất và 𝑥 ∈ [𝑙𝑜. . ℎ𝑖] thỏa hàm số 𝑓(𝑥). Điều kiện để có thể vận dụng phương pháp
tìm kiếm nhị phân (hay còn gọi là chặt nhị phân) cho bài toán này đó là ∀𝑥 < 𝑦: 𝑓(𝑥) ≤ 𝑓(𝑦) hoặc ∀𝑥 <
𝑦: 𝑓(𝑥) ≥ 𝑓(𝑦), nói cách khác hàm 𝑓(𝑥) là hàm đồng biến hoặc nghịch biến trên miền giá trị của 𝑥.
Ứng dụng: Cho số nguyên dương 𝑀(𝑀 ≤ 1018 ). Tìm số nguyên dương 𝑛 nhỏ nhất thỏa điều kiện
1 + 2 + ⋯ + 𝑛 ≥ 𝑀(∗)
Lời giải:
Bản chất của bài toán là tìm kiếm giá trị 𝑛 nhỏ nhất thỏa điều kiện bất phương trình (∗).
Cách 2:
- Gọi 𝑆(𝑛) = 1 + 2 + ⋯ + 𝑛. Ta có ∀𝑛1 < 𝑛2 : 𝑆(𝑛1 ) < 𝑆(𝑛2 ). Vậy ta có thể áp dụng kỹ thuật chặt nhị
phân giá trị 𝑛 trong đoạn [1. . √8 × 𝑀].
ll Solve(ll M)
{
ll lo = 1, hi = sqrt(8*M), n;
while (lo <= hi)
{
ll x = (lo + hi)/2;
if (Sum(x) >= M)
{
hi = x-1;
n = x; //n là nghiệm ứng viên
}
else lo = x+1;
}
return n;
}
Thuật toán chặt nhị phân giá trị 𝑛 trong đoạn từ 1 đến √8 × 𝑀 nên có độ phức tạp 𝑂(log √8 × 𝑀).
2) Cho dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và số nguyên 𝑥. Hãy đếm số cặp phần tử 𝑖, 𝑗 (1 ≤ 𝑖 < 𝑗 ≤ 𝑛) sao
cho 𝑎𝑖 + 𝑎𝑗 ≤ 𝑥.
Dữ liệu: Vào từ tập tin văn bản CHAP0402.INP gồm 2 dòng
- Dòng đầu chứa 2 số nguyên dương 𝑛, 𝑥(𝑛 ≤ 106 ; |𝑥| ≤ 2 × 106 ).
- Dòng tiếp theo chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản CHAP0402.OUT một số nguyên là số cặp (𝑖, 𝑗) sao cho 𝑎𝑖 + 𝑎𝑗 ≤ 𝑥.
Ví dụ:
CHAP0402.INP CHAP0402.OUT
6 9 8
4 6 13 5 1 3
3) Cho 2 tập họp được biểu diễn bằng dãy các số nguyên có giá trị phân biệt nhau đôi một 𝑎1 , 𝑎2 , … , 𝑎𝑛
và 𝑏1 , 𝑏2 , … , 𝑏𝑚 . Hãy tìm phần hội, giao và hiệu của tập {𝑎𝑖 } với tập {𝑏𝑗 }.
Dữ liệu: Vào từ tập tin văn bản CHAP0403.INP gồm 3 dòng
- Dòng đầu tiên chứa 2 số nguyên dương 𝑛, 𝑚(1 ≤ 𝑛, 𝑚 ≤ 105 ).
- Dòng thứ hai chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 105 ).
- Dòng thứ ba chứa dãy 𝑏1 , 𝑏2 , … , 𝑏𝑛 (|𝑏𝑗 | ≤ 105 ).
Kết quả: Ghi ra tập tin văn bản CHAP0403.OUT trên 3 dòng
- Dòng thứ nhất ghi kết quả của phép hội.
- Dòng thứ hai ghi kết quả của phép giao.
4) Cho 2 dãy số nguyên {𝑎𝑖 } và {𝑏𝑗 }. Hãy trộn các phần tử của 2 dãy để tạo thành một dãy mới có thứ
tự tăng dần.
Dữ liệu: Vào từ tập tin văn bản CHAP0404.INP gồm 3 dòng
- Dòng đầu tiên chứa 2 số nguyên 𝑛, 𝑚(1 ≤ 𝑛, 𝑚 ≤ 105 ).
- Dòng thứ hai chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 106 ).
- Dòng thứ ba chứa dãy 𝑏1 , 𝑏2 , … , 𝑏𝑚 (|𝑏𝑗 | ≤ 106 ).
Kết quả: Ghi ra tập tin văn bản CHAP0404.OUT gồm 𝑛 + 𝑚 số nguyên trên một dòng là dãy sau
khi ghép.
Ví dụ:
CHAP0404.INP CHAP0404.OUT
6 5 1 2 3 4 6 7 8 10
8 2 4 7 1 10 2 4 8
3 4 2 8 6 1 7 10
5) Cho 2 dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và 𝑏1 , 𝑏2 , … , 𝑏𝑚 . Tìm các giá trị xuất hiện ở cả 2 dãy.
Dữ liệu: Vào từ tập tin văn bản CHAP0405.INP gồm 3 dòng
- Dòng đầu tiên chứa 2 số nguyên 𝑛, 𝑚(1 ≤ 𝑛, 𝑚 ≤ 105 ).
- Dòng thứ hai chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 109 ).
- Dòng thứ ba chứa dãy 𝑏1 , 𝑏2 , … , 𝑏𝑚 (|𝑏𝑗 | ≤ 109 ).
Kết quả: Ghi ra tập tin văn bản CHAP0405.OUT trên một dòng gồm các giá trị chung của 2 dãy. Các
giá trị được ghi theo thứ tự tăng dần.
Ví dụ:
CHAP0405.INP CHAP0405.OUT
6 5 2 4 8
8 2 4 7 1 10
3 4 2 8 6
11) Cho 2 dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và 𝑏1 , 𝑏2 , … , 𝑏𝑚 . Tìm giá trị nhỏ nhất của |𝑎𝑖 − 𝑏𝑗 |(1 ≤ 𝑖 ≤
𝑛; 1 ≤ 𝑗 ≤ 𝑚).
Dữ liệu: Vào từ tập tin văn bản CHAP0411.INP gồm 3 dòng
- Dòng đầu tiên chứa 2 số nguyên 𝑛, 𝑚(1 ≤ 𝑛, 𝑚 ≤ 105 ).
- Dòng thứ hai chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 109 ).
- Dòng thứ ba chứa dãy 𝑏1 , 𝑏2 , … , 𝑏𝑚 (|𝑏𝑗 | ≤ 109 ).
Kết quả: Ghi ra tập tin văn bản CHAP0411.OUT một số nguyên là kết quả tìm được.
12) Cho 2 dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và 𝑏1 , 𝑏2 , … , 𝑏𝑚 . Tìm giá trị nhỏ nhất của |𝑎𝑖 + 𝑏𝑗 |
(1 ≤ 𝑖 ≤ 𝑛; 1 ≤ 𝑗 ≤ 𝑚).
Dữ liệu: Vào từ tập tin văn bản CHAP0412.INP gồm 3 dòng
- Dòng đầu tiên chứa 2 số nguyên 𝑛, 𝑚(1 ≤ 𝑛, 𝑚 ≤ 105 ).
- Dòng thứ hai chứa dãy 𝑎1 , 𝑎2 , … , 𝑎𝑛 (|𝑎𝑖 | ≤ 109 ).
- Dòng thứ ba chứa dãy 𝑏1 , 𝑏2 , … , 𝑏𝑚 (|𝑏𝑗 | ≤ 109 ).
Kết quả: Ghi ra tập tin văn bản CHAP0412.OUT một số nguyên là kết quả tìm được.
Ví dụ:
CHAP0412.INP CHAP0412.OUT
3 4 1
4 10 2
3 7 5 -1
13) Cho mảng 2 chiều kích thước 𝑛 × 𝑚. Hãy tìm vị trí 𝑖, 𝑗(0 ≤ 𝑖 < 𝑛; 0 ≤ 𝑗 < 𝑚) sao cho tổng tất cả
phần tử nằm trên dòng 𝑖 và cột 𝑗 là lớn nhất. Nếu có nhiều kết quả thì ghi vị trí 𝑖, 𝑗 sao cho 𝑖 + 𝑗 là nhỏ
nhất.
Dữ liệu: Vào từ tập tin văn bản CHAP413.INP
- Dòng đầu chứa 2 số nguyên dương 𝑛, 𝑚(1 ≤ 𝑛, 𝑚 ≤ 103 ).
- Mỗi dòng trong 𝑛 dòng tiếp theo chứa dãy gồm 𝑚 số nguyên mô tả 1 dòng tương ứng của mảng.
Kết quả: Ghi ra tập tin văn bản CHAP0413.OUT 2 số nguyên 𝑖, 𝑗 tìm được.
Ví dụ:
CHAP0413.INP CHAP0413.OUT
3 4 1 3
5 2 -4 10
-3 1 8 -6
2 -1 8 3
Kỹ thuật mảng thống kê lưu tần số xuất hiện của giá trị 𝑥 tại vị trí 𝑥 trong mảng thống kê cnt, nói cách
khác cnt[x] cho biết tần số xuất hiện của giá trị 𝑥 trong dãy. Mảng thống kê cnt được xây dựng như
sau
cnt[maxV+1] = {0};
Phương pháp mảng thống kê thực hiện các yêu cầu với độ phức tạp tuyến tính. Kích thước của mảng cnt
chính là độ lớn miền giá trị của 𝑥 (giá trị phần tử dãy). Do đó nếu miền giá trị của các phần tử trong dãy
quá lớn thì không thể áp dụng phương pháp này. Phương pháp mảng thống kê chỉ hiệu quả và khả thi khi
độ lớn miền giá trị của các phần tử trong dãy cỡ 107 .
Bài toán 1: Cho dãy số nguyên gồm 𝑛 phần tử thỏa điều kiện 𝑎1 ≤ 𝑎2 ≤ ⋯ ≤ 𝑎𝑛 . Hãy loại các giá trị
trùng lắp ra khỏi dãy.
void RemoveDup1()
{
if (n == 0) return 0;
for (int i = 1; i < n;)
{
if ( a[i] == a[i-1])
{
for (int j = i+1; j < n; j++)
a[j-1] = a[j];
n--;
void RemoveDup2()
{
if (n == 0) return 0;
int i = 0;
for (int j = 1; j < n; j++)
{
if (a[i] != a[j])
a[++i] = a[j];
}
n = i+1;
}
Bài toán 2: Cho 2 dãy số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và 𝑏1 , 𝑏2 , … , 𝑏𝑚 . Hãy kiểm tra dãy {𝑎𝑖 } có phải là một dãy
con gồm các phần tử không liên tiếp nhau của dãy {𝑏𝑗 }?
bool CheckSub()
{
for (int j = 0, i = 0; j < m; j++)
if (a[i] == b[j])
{
i++;
if (i == n) return true;
}
return false;
}
Bài toán 3: Cho dãy số nguyên dương 𝑎1 , 𝑎2 , … , 𝑎𝑛 và số nguyên dương 𝑆. Hãy chỉ ra một dãy con gồm
các phần tử liên tiếp sao cho tổng của chúng đúng bằng 𝑆.
Nhận xét: một dãy con liên tiếp là dãy gồm các phần tử nằm từ vị trí 𝑖 đến vị trí 𝑗(1 ≤ 𝑖 ≤ 𝑗 ≤ 𝑛).
Cách 1: Sử dụng phương pháp vét cạn. Xét tất cả cặp vị trí (𝑖 ≤ 𝑗) và kiểm tra tổng 𝑎𝑖 + 𝑎𝑖+1 + ⋯ + 𝑎𝑗
có bằng 𝑆. Độ phức tạp 𝑂(𝑛2 )
int a[maxN], n;
Bài toán 1: Cho chuỗi kí tự, đảo ngược thứ tự các kí tự của chuỗi
Bài toán 2: Cho chuỗi 𝑠 chỉ gồm các kí tự chữ cái in thường. Hãy kiểm tra xem 𝑠 có phải là chuỗi
Palindrome hay không? Chuỗi rỗng được xem là chuỗi Palindrome
Bài toán 3: Cho dãy gồm 𝑛 số nguyên thỏa 𝑎1 ≤ 𝑎2 ≤ ⋯ ≤ 𝑎𝑛 và số nguyên 𝑥. Hãy tìm vị trí 2 phần tử
khác nhau của dãy có tổng đúng bằng 𝑥.
Bài toán 4: Cho dãy gồm 𝑛 số nguyên 𝑎1 , 𝑎2 , … , 𝑎𝑛 và số nguyên 𝑘. Hãy thực hiện xoay dãy 𝑘 bước, mỗi
bước đưa phần tử ở cuối dãy về đầu dãy. Ví dụ dãy 𝑛 = 7 phần tử {1,2,3,4,5,6,7} và 𝑘 = 3, dãy biến đổi
1 2 3
qua 3 bước như sau {1,2,3,4,5,6,7} → {7,1,2,3,4,5,6} → {6,7,1,2,3,4,5} → {5,6,7,1,2,3,4}.
void rotate1(int k)
{
for (int i = 1; i <= k; i++)
{
int x = a[n-1];
for (int j = n-1; j > 0; j--)
a[j] = a[j-1];
a[0] = x;
}
}
Ý tưởng: khi thực hiện một phép xoay phải dãy thì mỗi phần tử của dãy đều bị dịch tới 1 vị trí, do đó khi
thực hiện 𝑘 phép xoay phải thì phần tử thứ 𝑖 của dãy bị dịch tới vị trí 𝑖 + 𝑘.
int a[maxN], n;
void rotate2(int k)
{
vector<int> b(n);
for (int i = 0; i < n; i++) b[(i+k) % n] = a[i];
void rotate3(int k)
{
k = k % n;
reverse(a, 0, n-k-1);
reverse(a, n-k, n-1);
reverse(a, 0, n-1);
}
Bài toán 2: Cho bảng số nguyên 𝐴 kích thước 𝑚 × 𝑛(1 ≤ 𝑚, 𝑛 ≤ 103 ). Hãy đếm số ô trong bảng thỏa
tính chất giá trị tại ô đó lớn hơn giá trị tất cả ô chung cạnh và chung đỉnh xung quanh.
Đoạn chương trình 1: Sử dụng câu lệnh rẽ nhánh
int Count(int m, int n)
{
int ans = 0;
for (int i = 1; i <= m; ++i)
for (int j = 1; j <= n; ++j)
{
if (A[i][j] > A[i][j-1] && A[i][j] > A[i-1][j-1] &&
A[i][j] > A[i-1][j] && A[i][j] > A[i-1][j+1] &&
A[i][j] > A[i][j+1] && A[i][j] > A[i+1][j+1] &&
A[i][j] > A[i+1][j] && A[i][j] > A[i+1][j-1]) ++ans;
}
return ans;
}
Nhận xét: Kỹ thuật mảng hằng giúp rút gọn các điều kiện được xét trong câu lệnh rẽ nhánh hoặc tránh
việc xét quá nhiều câu lệnh rẽ nhánh. Điều này giúp cho chương trình giảm thiểu các lỗi khi xét các điều
kiện phức hợp và trong một số trường hợp có thể giúp chương trình chạy nhanh hơn.
Ngôn ngữ C/C++ không hỗ trợ xử lý tính toán trên các số nguyên có miền giá trị lớn hơn 263 − 1. Do đó,
ta cần phải cài đặt các thao tác tính toán cơ bản trên các số nguyên lớn để phục vụ giải các bài toán liên
quan.
Có nhiều cách để biểu diễn một số nguyên lớn, ta có thể coi số nguyên lớn như là một dãy các chữ số, do
đó có thể sử dụng xâu kí tự, mảng một chiều hoặc danh sách liêt kết để biểu diễn một số nguyên lớn. Tài
liệu này sử dụng mảng một chiều để biểu diễn một số nguyên lớn vì tính đơn giản trong cài đặt.
Nhận xét rằng các phép toán cộng, trừ, nhân trên số nguyên đều thực hiện từ hàng đơn vị trở đi. Do đó,
để thuận tiện cho việc cài đặt, số nguyên lớn được biểu diễn thành mảng với các chữ số có thứ tự ngược
lại với giá trị thực sự của nó. Chẳng hạn số nguyên 3000 được biểu diễn như sau:
0 0 0 3
0 1 2 3
Khai báo số nguyên lớn
Các phép toán trên số nguyên lớn được cài đặt theo cách tính toán bằng tay thông thường. Dưới đây ta
xét một số phép toán cơ bản đối với số nguyên lớn không dấu.
res.push_back(cr%10);
cr = cr / 10;
}
if (cr > 0) res.push_back(cr);
return res;
}