Tải bản đầy đủ (.doc) (61 trang)

Sáng kiến kinh nghiệm tin học _kỷ thuật chặt nhị phân

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (230.29 KB, 61 trang )

PHẦN I: MỞ ĐẦU
1. Lí do chọn đề tài
Qua thực tiễn giảng dạy bộ môn tin học ở THPT, đặc biệt quá trình bồi dưỡng học
sinh dự thi chọn HSG tỉnh tôi nhận thấy để đạt được điểm tối đa trong các kỳ thi, ngoài
việc tìm ra thuật toán học sinh còn phải tìm được thuật toán giải quyết bài toán nhanh,
đáp ứng yêu cầu của tất cả các bộ test khi chấm.
Trong quá trình giảng dạy, tôi thấy có nhiều bài toán học sinh có thể dễ dàng tìm ra
cách giải, tuy nhiên nó chỉ có hiệu quả đối với các trường hợp test đơn giản. Đặc biệt với
các bài toán tìm kiếm tối ưu, nhiều trường hợp bài toán chỉ giải quyết được hết các yêu
cầu khi áp dụng thuật toán tìm kiếm nhị phân.
Nội dung thuật toán tìm kiếm nhị phân đã được trình bày ở Sách giáo khoa Tin học
10, phần thuật toán và Sách giáo khoa tin học 11 khi giảng dạy về Pascal. Tuy nhiên nếu
chỉ nắm bắt được những nội dung này thì học sinh chỉ áp dụng được một bài toán đơn
giản là tìm số nguyên k trong 1 dãy số đã sắp xếp. Hiện nay thì tài liệu tham khảo để
hướng dẫn việc áp dụng thuật toán tìm kiếm nhị phân cũng rất ít. Trong quá trình tham
gia các diễn đàn, giải bài tập, đề thi trên mạng, tôi đã tổng hợp được một số cách nhận
dạng và kỷ thuật cài đặt thuật toán tìm kiếm nhị phân để giải quyết các bài toán tìm kiếm
một cách tối ưu – Kỷ thuật “chặt nhị phân”. Chính vì những lý do trên mà tôi lựa chọn đề
tài Kỷ thuật “chặt nhị phân” trong bài toán tìm kiếm để tìm hiểu và vận dụng vào quá
trình bồi dưỡng học sinh giỏi của mình.
2. Mục đích nghiên cứu
Trong phạm vi đề tài của mình, tôi muốn nghiên cứu về kỷ thuật “chặt nhị phân”.
Từ việc tìm hiểu một bài toán đơn giản trong sách giáo khoa để linh hoạt cài đặt và giải
quyết các bài toán tìm kiếm một cách tối ưu nhất. Với các đối tượng học sinh khá giỏi sẻ
biết cách nhận dạng và áp dụng kỷ thuật này để giải quyết các bài toán tìm kiếm với độ
phức tạp thấp nhất, đáp ứng hết tất cả các yêu cầu về giới hạn dữ liệu vào. Cũng qua đề
tài này, tôi muốn được cùng các đồng nghiệp nghiên cứu, tìm hiểu, trau dồi chuyên môn,
nghiệp vụ và nhất là niềm đam mê lập trình.
3. Đối tượng nghiên cứu
- Kỷ thuật chặt nhị phân và cách áp dụng để giải các bài toán tìm kiếm.
- Phân tích, đánh giá độ phức tạp thuật toán và so sánh với cách giải khác để đưa ra


ưu điểm của kỷ thuật chặt nhị phân.
4. Phạm vi nghiên cứu
- Kỷ thuật chặt nhị phân và cách áp dụng để giải các bài toán tìm kiếm tối ưu.
- Phạm vi ứng dụng: Đối với học sinh khá, giỏi, chủ yếu phục vụ quá trình bồi
dưỡng học sinh giỏi.
5. Giả thuyết khoa học
Từ kiến thức Sách giáo khoa và vận dụng các kinh nghiệm khi tham gia giải các đề
thi để áp dụng kỷ thuật chặt nhị phân một cách có hiệu quả.
6. Nhiệm vụ nghiên cứu
- Tìm hiểu các kiến thức về kỷ thuật chặt nhị phân
1


- Đưa ra được các bài tập và cách giải thông thường của học sinh, so sánh với việc
áp dụng kỷ thuật chặt nhị phân để thấy rõ hiệu quả của việc áp dụng kỷ thuật này. Từ đó
hình thành cho học sinh cách tư duy và đức tính cẩn thận, luôn cố gắng để giải quyết các
bài toán một cách tối ưu.
7. Phương pháp nghiên cứu
- Phương pháp nghiên cứu lý thuyết: nghiên cứu sách, báo và các tài liệu điện tử,
website về lập trình.
- Phương pháp phỏng vấn chuyên gia: tiếp thu ý kiến cố vấn, đánh giá của các giáo
viên có kinh nghiệm..
- Phương pháp so sánh: So sánh, đối chiếu giữa các chương trình mẫu và chương
trình học sinh cài đặt, từ đó rút ra tính tối ưu của kỷ thuật chặt nhị phân.
- Tham khảo ý kiến của đồng nghiệp.
- Phương pháp thực nghiệm: Áp dụng vào giảng dạy, bồi dưỡng học sinh giỏi Tin
học khối 11,12.
8. Dự kiến đóng góp của đề tài
- Về lý luận: Hệ thống được một số bài tập, cách nhận diện và kỷ thuật áp dụng
thuật toán tìm kiếm nhị phân. So sánh độ phức tạp của chương trình để thấy hiệu quả của

việc áp dụng thuật toán này.
- Về thực tiễn: Sử dụng đề tài làm tài liệu tham khảo để giảng dạy bộ môn tin học,
đặc biệt là quá trình bồi dưỡng, luyện thi học sinh giỏi.
PHẦN II. GIẢI QUYẾT VẤN ĐỀ
1. Cơ sở lý luận
Bản chất của kỷ thuật “chặt nhị phân” là thuật toán tìm kiếm nhị phân được trình
bày ở phần thuật toán, sách giáo khoa tin học lớp 10. Bài toán cơ sở như sau:
Cho dãy số nguyên A tăng dần và 1 số nguyên K. Đưa ra chỉ số i mà Ai=k hoặc
thông báo không có số hạng nào của dãy A có giá trị bằng k.
- Ý tưởng: Sử dụng tính chất dãy A là dãy số tăng dần, ta tìm cách thu hẹp nhanh
phạm vi tìm kiếm sau mỗi lần so sánh khóa với số hạng được chọn. Để làm điều đó ta
chọn số hạng Agiua ở giữa dãy để so sánh với K, trong đó Giua=[N+1] div 2.
Khi đó chỉ xảy ra một trong ba trường hợp sau:
- Nếu Agiua=k thì giua là chỉ số cần tìm. Việc tìm kiếm kết thúc.
- Nếu Agiua>k thì việc tìm kiếm tiếp theo chỉ xét trên dãy A 1, A2, …, Agiua-1 (Phạm vi
tìm kiếm mới bằng khoảng một nửa phạm vi tìm kiếm trước đó).
- Nếu AgiuaQuá trình trên sẻ được lặp lại một số lần cho đến khi hoặc đã tìm thấy khóa K trong
dãy hoặc phạm vi tìm kiếm bằng rỗng.
Thuật toán tìm kiếm nhị phân có độ phức tạp về thời gian là O(logN), so với tìm
kiếm tuần tự độ phức tạp là O(N) thì nó đã giải quyết được các test có N>10 3 mà thuật
toán tìm kiếm tuần tự bị bắt các lỗi chạy quá thời gian quy định. Tuy nhiên chỉ áp dụng

2


tìm kiếm nhị phân khi dãy đã sắp xếp nên chúng ta thường sử dụng thuật toán sắp xếp
nhanh (Qsort) với độ phức tạp O(NlogN) để sắp xếp dãy trước đó.
2. Cơ sở thực tiễn.
Hầu hết các học sinh sau khi học xong thuật toán tìm kiếm nhị phân chỉ áp dụng

được vào giải các bài toán có yêu cầu tương tự. Một phần vì nguồn tài liệu tham khảo,
tổng hợp các dạng bài liên quan để học sinh áp dụng hầu như không có. Do đó học sinh
không được trải nghiệm, hình thành tư duy và áp dụng thuật toán này vào giải các bài
toán. Trong quá trình tham gia các diễn đàn về lập trình, các website dạy lập trình như
spoj, upcoder, codeforces tôi đã có nhiều cơ hội để tìm tòi và áp dụng kỷ thuật chặt nhị
phân vào để giải quyết các bài toán. Từ đó tôi mong muốn có một nguồn tài liệu để đồng
nghiệp, học sinh khá, giỏi tham khảo và tìm hiểu cũng như rèn luyện kỷ thuật này vào
quá trình dạy, học và lập trình.
3. Kỷ thuật “Chặt nhị phân”
3.1. Hiệu quả của chặt nhị phân.
Để hiểu rõ được sự cần thiết của kỷ thuật “chặt nhị phân”, đầu tiên tôi đưa ra một số
bài toán và cách suy nghĩ, thuật toán và chương trình của học sinh. Sau đó phân tích về
độ lớn của dữ liệu input và độ phức tạp thuật toán. Cuối cùng là so sánh với chương trình
giải quyết bài toán đó khi áp dụng kỷ thuật “chặt nhị phân”.
Bài 1: Cho dãy A có N số. Tìm số lượng các số có giá trị nhỏ hơn số nguyên M.
Dữ liệu vào: Tệp ‘slbehon.inp’
- Dòng đầu tiên chứa số nguyên N cho biết số lượng phần tử của mảng A.
- Dòng thứ hai chứa N số nguyên cách nhau.
- Dòng thứ ba chứa số lượng Test Q.
- Mỗi dòng Q tiếp theo chứa số nguyên M.
Dữ liệu ra: Tệp ‘slbehon.out’:
- Đối với mỗi truy vấn số lượng các số có giá trị nhỏ hơn M cho test đó.
Giới hạn:
1 ≤ N ≤ 105
1 ≤ A [i] ≤ 109
1 ≤ Q ≤ 105
1 ≤ M ≤ 105
Slbehon.inp
5
1 4 10 5 6

4
2
3
5
11

Slbehon.out
1
1
2
5

3


Cách 1: Nhìn qua đây là một bài toán đơn giản và học sinh thường giải theo cách
sau đây: Xây dựng 1 hàm để đếm số lượng phần tử trong mảng A có giá trị bé hơn M,
hàm này được viết thường là duyệt tuần tự từ A 1 đến AN, so sánh Ai với M và tăng biến
lưu giá trị đếm. Chương trình được viết như sau:
program slbehonc1;
const fi='slbehon.inp';
fo='slbehon.out';
nmax=100000;
var
a:array[1..nmax] of longint;
n,m,q:longint;
f,f1:text;
function dem(m:longint):longint;
var i:longint;
begin

dem:=0;
for i:=1 to n do
if a[i]end;
procedure doc;
var i:integer;
x:longint;
begin
assign(f1,fo);
rewrite(f1);
assign(f,fi);
reset(f);
readln(f,n);
for i:=1 to n do read(f,a[i]);
readln(f);
readln(f,q);
for i:=1 to q do
begin
readln(f,x);
writeln(f1,dem(x));
end;
end;
begin
doc;
close(f1);
end.
Với cách giải trên ta có: Hàm Dem có độ phức tạp O(N) và bài toán có Q test nên
độ phức tạp là Q.O(N). Với yêu cầu 1 ≤ N ≤ 105; 1 ≤ Q ≤ 105 thì cách giải trên không
thực hiện hết được các test lớn có Q, N tối đa.
4



Cách 2: Để giải quyết vấn đề này hiệu quả chúng ta phải giải quyết nó trong
O(Nlogn) thời gian phức tạp. Ta thực hiện sắp xếp mảng đã cho bằng thuật toán Qsort và
áp dụng kỷ thuật “chặt nhị phân” để xác định chỉ số cuối cùng của phần tử nhỏ hơn M.
program So_luong_be_hon;
const fi='slbehon.inp';
fo='slbehon.out';
nmax=100000;
var
a:array[1..nmax] of longint;
n,m,q:longint;
f,f1:text;
procedure qsort(l,h:integer);
var i,j,tam,k:longint;
begin
i:=l;
j:=h;
k:=a[(l+h) div 2];
repeat
while a[i]while a[j]>k do dec(j);
if i<=j then
begin
tam:=a[i];
a[i]:=a[j];
a[j]:=tam;
inc(i);
dec(j);
end;

until i>j;
if iif j>l then qsort(l,j);
end;
function tknp(x:longint):longint;
var d,c,giua,res:longint;
begin
res:=-1;
d:=1;
c:=n;
while d<=c do
begin
giua:=(d+c)div 2;
if a[giua]>=x then c:=giua-1
else
if a[giua]begin
res:=giua;
d:=giua+1;
end;
5


end;

end;
if res=-1 then tknp:=0
else tknp:=res;

procedure doc;

var i:integer;
x:longint;
begin
assign(f1,fo);
rewrite(f1);
assign(f,fi);
reset(f);
readln(f,n);
for i:=1 to n do read(f,a[i]);
qsort(1,n);
readln(f);
readln(f,q);
for i:=1 to q do
begin
readln(f,x);
writeln(f1,tknp(x));
end;
end;
begin
doc;
close(f1);
end.
Bài 2. Cho 1 dãy N số khác nhau. Hãy tìm xem có bao nhiêu cặp số có chênh lệch
là k đơn vị.
Dữ liệu vào: Tệp ‘chenhlech.inp’:
- Dòng đầu tiên là N - số lượng dãy số và số nguyên K (N≤105, K≤109)
- Dòng tiếp theo chứa N số nguyên trong dãy. (A[i] ≤ 109)
Dữ liệu ra: Tệp ‘chenhlech.out’
- Gồm một số duy nhất là số cặp có độ chênh lệch k.
Capso.inp

62
132495

Capso.out
3

Giải thích test.
3 cặp số đó là; (1,3) (3,5) và (2,4)

Cách 1: Thông thường học sinh sẻ đưa ra cách giải sau: Sử dụng 2 vòng For để
duyệt và tìm ra 1 cặp số chênh lệch k đơn vị thì tăng biến đếm.
program capso;
const fi='capso.inp';
fo='capso.out';
6


var

const nmax=100000;
f:text;
n,k,i,j,dem:longint;
a:array[1..nmax] of longint;

procedure doc;
var i:longint;
begin
assign(f,fi);
reset(f);
readln(f,n,k);

for i:=1 to n do read(f,a[i]);
end;
begin
doc;
dem:=0;
for i:=1 to n-1 do
for j:=i+1 to n do
if abs(a[i]-a[j])=k then inc(dem);
assign(f,fo);
rewrite(f);
writeln(f,dem);
close(f);
end.
Chương trình trên có độ phức tạp là O(N 2). Với giới hạn input của bài toán là
(N≤105) thì chương trình trên không thực hiện được với test tối đa.
Cách 2: Sử dụng kỷ thuật “chặt nhị phân”. Đầu tiên ta sắp xếp dãy tăng dần bằng
Qsort. Tiếp theo ta duyệt dãy A. Tại Ai, nếu hàm TKNP(A[i]-k)=true thì ta tăng biến đếm
(tìm được 1 số có chênh lệch k so với A[i]). Độ phức tạp O(NlogN).
program capso;
const fi='capso.inp';
fo='capso.out';
nmax=100000;
var
f:text;
n,k,dem:longint;
a:array[1..nmax] of longint;
procedure doc;
var i:longint;
begin
assign(f,fi);

reset(f);
readln(f,n,k);
for i:=1 to n do read(f,a[i]);
end;
procedure sort(l,h:longint);
var
7


i,j,tam,x:longint;
begin
i:=l;
j:=h;
x:=a[(l+h) div 2];
repeat
while a[i]while xif i<=j then
begin
tam:=a[i];
a[i]:=a[j];
a[j]:=tam;
inc(i);
dec(j);
end;
until i>j;
if lif iend;
function tknp(i:longint):boolean;

var d,c,g:longint;
begin
d:=1;
c:=n;
while d<=c do
begin
g:=(d+c) div 2;
if (a[g]=i) then
begin
exit(true);
end;
if a[g]else c:=g-1;
end;
exit(false);
end;
procedure xuli;
var i:longint;
begin
sort(1,n);
for i:=1 to n do
if (tknp(a[i]-k)) then inc(dem);
end;
begin
doc;
dem:=0;
8


end.


xuli;
assign(f,fo);
rewrite(f);
write(f,dem);
close(f);

Bài 3. Trò chơi với dãy số
Hai bạn học sinh trong lúc nhàn rỗi nghĩ ra trò chơi sau đây. Mỗi bạn chọn trước
một dãy số gồm n số nguyên. Giả sử dãy số mà bạn thứ nhất chọn là:
b1, b2, ..., bn
còn dãy số mà bạn thứ hai chọn là
c1, c2, ..., cn
Mỗi lượt chơi mỗi bạn đưa ra một số hạng trong dãy số của mình. Nếu bạn thứ nhất
đưa ra số hạng bi (1 ≤ i ≤ n), còn bạn thứ hai đưa ra số hạng c j (1 ≤ j ≤ n) thì giá của lượt
chơi đó sẽ là |bi+cj|.
Yêu cầu: Hãy xác định giá nhỏ nhất của một lượt chơi trong số các lượt chơi có thể.
Ví dụ: Giả sử dãy số bạn thứ nhất chọn là 1, -2; còn dãy số mà bạn thứ hai chọn là
2, 3. Khi đó các khả năng có thể của một lượt chơi là (1, 2), (1, 3), (-2, 2), (-2, 3). Như
vậy, giá nhỏ nhất của một lượt chơi trong số các lượt chơi có thể là 0 tương ứng với giá
của lượt chơi (-2, 2).
Dữ liệu vào và giới hạn: Tệp ‘tcds.inp’
-Dòng đầu tiên chứa số nguyên dương n (n ≤ 105)
-Dòng thứ hai chứa dãy số nguyên b1, b2, ..., bn (|bi| ≤ 109, i=1, 2, ..., n)
-Dòng thứ hai chứa dãy số nguyên c1, c2, ..., cn (|ci| ≤ 109, i=1, 2, ..., n)
Dữ liệu ra: Tệp ‘tcds.out’
- Ghi ra giá nhỏ nhất tìm được.
tcds.inp
2
1 -2

23

tcds.out
0

Cách 1: Thông thường học sinh sẻ nghĩ ngay đến cách duyệt 2 vòng For như sau:
For i:=1 to n do
begin
For j:=1 to n do
Tong:=abs(b[i]+c[j]);
If tongEnd;
Cách làm này có độ phức tạp là O(N2)nên chỉ chạy tốt với các trường hợp
N<=1000. Giới hạn dữ liệu bài toán này là N=10 5. Để giải quyết khó khăn này chúng ta
có thể áp dụng kỷ thuật “chặt nhị phân” như sau:
9


Bài toán yêu cầu với mỗi số b[i] cần tìm c[j] sao cho |b[i]+c[j]| nhỏ nhất. Như vậy
với mỗi B[i] ta tìm c[j] sao cho b[i]+c[j] có giá trị gần 0 nhất. Có thể hiểu là với mỗi b[i]
ta tìm c[j] sao cho c[j] gần –B[i] nhất.
Các bước thực hiện:
1. Sắp xếp lại mảng C
2. Với mỗi b[i] tìm kiếm nhị phân c[j] thỏa mãn c[j] gần –b[i] nhất.
Cách này có độ phức tạp O(NlogN)
Chương trình:
program tcdayso;
const fi='tcds.inp';
fo='tcds.out';
nmax=100000;

var
f:text;
b,c:array[0..nmax] of longint;
n,min:longint;
procedure doc;
var i:longint;
begin
assign(f,fi);
reset(f);
readln(f,n);
for i:=1 to n do read(f,b[i]);
readln(f);
for i:=1 to n do read(f,c[i]);
c[0]:=0;
end;
procedure qsort(l,h:longint);
var i,j,khoa,tam:longint;
begin
i:=l;
j:=h;
khoa:=c[(l+h) div 2];
repeat
while (c[i]while (c[j]>khoa) do dec(j);
if (i<=j) then
begin
tam:=c[i];
c[i]:=c[j];
c[j]:=tam;
inc(i);

dec(j);
end;
until (i>j);
if lif i10


end;
function tknp(x:longint):longint;
var d,c1,g:longint;
begin
d:=1;
c1:=n;
if c[1]>-x then
begin
tknp:=c[1];
exit;
end;
if c[n]<-x then
begin
tknp:=c[n];
exit;
end;
while d<=c1 do
begin
g:=(d+c1) div 2;
if c[g]=-x then
begin
tknp:=0;

writeln(f,0);
close(f);
halt;
end;
if (c[g]>-x) and (c[g-1]<-x) then
begin
if abs(x+c[g])< abs(x+c[g-1]) then
tknp:=c[g]
else tknp:=c[g-1];
exit;
end
else
if c[g]>-x then
begin
tknp:=c[g];
c1:=g-1;
end
else
d:=g+1;
end;
end;
procedure xuly;
var i,x1:longint;
begin
for i:=1 to n do
begin
11


x1:=tknp(b[i]);

if abs(x1+b[i])end;

end;
begin
doc;
min:=high(longint);
qsort(1,n);
assign(f,fo);
rewrite(f);
xuly;
writeln(f,min);
close(f);
end.
Bài 4. Chọn quân bài

Cho một tập N quân bài, mỗi quân chứa một số nguyên dương. Bạn cần phải chọn
ra ba quân bài sao cho tổng các số trên 3 quân bài gần với số M nhất và không vượt quá
M.
Dữ liệu vào: Tệp ‘quanbai.inp’:
Dòng 1 chứa 2 số N và M. (N <=100, M <= 500000)
Dòng 2 chứa N số nguyên dương, mỗi số không quá 100000.
Dữ liệu ra: Tệp ‘quanbai.out’:
In ra tổng 3 quân gần M nhất và không vượt quá M.
Input luôn đảm bảo tồn tại đáp số.
Quanbai.inp
5 21
56789

Quanbai.out

21

Cách 1: Duyệt 3 vòng For và tìm max. Độ phức tạp O(n3)
program quanbai;
const fi='quanbai.inp';
fo='quanbai.out';
var
f:text;
n,m,max:longint;
a:array[1..100] of longint;
procedure doc;
var i:longint;
begin
assign(f,fi);
reset(f);
readln(f,n,m);
for i:=1 to n do read(f,a[i]);
end;
12


procedure xuly;
var i,j,z:longint;
begin
for i:=1 to n-2 do
for j:=i+1 to n-1 do
for z:=j+1 to n do
if (a[i]+a[j]+a[z]>max) and (a[i]+a[j]+a[z]<=m)
then max:=a[i]+a[j]+a[z];
end;

begin
doc;
max:=0;
xuly;
assign(f,fo);
rewrite(f);
writeln(f,max);
close(f);
end.
Cách 2: Sử dụng thuật toán chặt nhị phân. Gọi a[z] là số lớn nhất trong bộ 3 số a[i],
a[j], a[z]. Tìm kiếm nhị phân số a[z] sao cho tổng 3 số <=M. Lưu ý cần sắp xếp mảng A
trước khi tìm kiếm. Độ phức tạp O(N2logN)
program quanbai;
const fi='quanbai.inp';
fo='quanbai.out';
var
f:text;
n,m,max:longint;
a:array[1..100] of longint;
procedure doc;
var i:longint;
begin
assign(f,fi);
reset(f);
readln(f,n,m);
for i:=1 to n do read(f,a[i]);
end;
function tknp(i,j:longint):longint;
var d,c,g,s:longint;
begin

tknp:=0;
d:=j+1;
c:=n;
while d<=c do
begin
g:=(d+c) div 2;
s:=a[i]+a[j]+a[g];
if s>m then c:=g-1
13


else
if s=m then
begin
tknp:=a[g];
exit;
end
else
begin
tknp:=a[g];
d:=g+1;
end;
end;
end;
procedure qsort(l,h:integer);
var i,j,tam,k:longint;
begin
i:=l;
j:=h;
k:=a[(l+h) div 2];

repeat
while a[i]while a[j]>k do dec(j);
if i<=j then
begin
tam:=a[i];
a[i]:=a[j];
a[j]:=tam;
inc(i);
dec(j);
end;
until i>j;
if iif j>l then qsort(l,j);
end;
procedure xuly;
var i,j,z:longint;
begin
for i:=1 to n-2 do
for j:=i+1 to n-1 do
begin
z:=tknp(i,j);
if z=0 then break
else
if (a[i]+a[j]+z>max) and (a[i]+a[j]+z<=m) then
max:=a[i]+a[j]+z;
end;
end;
begin
14



doc;
qsort(1,n);
max:=0;
xuly;
assign(f,fo);
rewrite(f);
writeln(f,max);
close(f);
end.
3.2. Một số bài toán áp dụng kỷ thuật “chặt nhị phân”.
Các bài toán áp dụng kỷ thuật chặt nhị phân thường là bài toán tìm giá trị tối ưu nhỏ
nhất hoặc lớn nhất. Tùy thuộc vào yêu cầu bài toán và cách viết code của từng người mà
chương trình có thể khác nhau. Tuy vậy, tôi có thể đưa ra một cấu trúc như sau:
- Trường hợp tìm giá trị bé nhất trong đoạn 1,N:
Procedure chat_nhi_phan;
Var dau,cuoi,giua:longint;
Begin
Dau:=1;
Cuoi:=n;
While dau<=cuoi do
Begin
Giua=(dau+cuoi) div 2;
If kiemtra(giua) then
Begin
Res:=giua;
Cuoi:=giua-1;
End
Else dau:=giua+1;

End;
End;
-

Trường hợp tìm giá trị lớn nhất
Procedure chat_nhi_phan;
Var dau,cuoi,giua:longint;
Begin
Dau:=1;
Cuoi:=n;
While dau<=cuoi do
15


Begin
Giua=(dau+cuoi) div 2;
If kiemtra(giua) then
Begin
Res:=giua;
Dau:=giua+1;
End
Else cuoi:=giua-1;
End;
End;
Có thể thêm vào một số câu lệnh để kiểm tra nhanh kết quả và cũng có thể sử
dụng hàm để thực hiện tìm kiếm.
Bài 1. Cho một ma trận B có kích thước N × M, chọn một phần tử từ mỗi hàng để
tạo một mảng A mới có kích thước N (phần tử được chọn từ hàng 1, sẽ trở thành A[1],
phần tử được chọn từ hàng 2 sẽ trở thành A[2],…) Hãy tìm giá trị chênh lệch tối thiểu của
các phần tử trong hai hàng kề nhau.

Dữ liệu vào: Tệp ‘mangmoi.inp’
- Dòng đầu tiên bao gồm hai số nguyên cách nhau bởi dấu cách N và M.
- N dòng tiếp theo mỗi dòng chứa N số nguyên dương.
Kết quả: Tệp ‘mangmoi.out’
- Số duy nhất là độ chênh lệch tối thiểu giữa các hàng liền kề trong mảng A
Giới hạn:

2≤N, M≤1000; 1≤B[i,j]≤109

Mangmoi.inp
22
84
68

Mangmoi.out Giải trình
0
Độ chênh lệch tối thiểu là 0, có được
bằng cách chọn 8 từ cả hai hàng.

Ý tưởng: Trước hết ta thực hiện sắp xếp mỗi hàng của ma trận. Sử dụng biến cl để
cập nhật độ chênh lệch tối thiểu giữa hai hàng của mảng A.
Bắt đầu từ hàng đầu tiên đến hàng thứ hai của ma trận, với mỗi phần tử B[i,j] của
dòng hiện tại trong ma trận, tìm phần tử nhỏ nhất trong hàng kế tiếp lớn hơn hoặc bằng
phần tử hiện tại (max1) và phần tử lớn nhất nhỏ hơn phần tử hiện tại (min1). Công việc
này có thể được thực hiện bằng cách áp dụng kỷ thuật “chặt nhị phân”.
Bây giờ, chỉ cần cập nhật lại giá trị biến cl với min (cl, min(max1- B[i,j], B[i,j]–
min1)).
program mangmoi;
const nmax=1000;
fi='mangmoi.inp';

16


fo='mangmoi.out';
var B:array[1..nmax,1..nmax] of longint;
n,m,i,j,max1,min1,cl:longint;
a:array[1..nmax] of longint;
f:text;
procedure doc;
var i,j:longint;
begin
assign(f,fi);
reset(f);
readln(f,n,m);
for i:=1 to n do
begin
for j:=1 to m do
read(f,b[i,j]);
readln(f);
end;
end;
procedure qsort(k,l,h:integer);
var i,j,tam,x:longint;
begin
i:=l;
j:=h;
x:=b[k,(l+h) div 2];
repeat
while b[k,i]while b[k,j]>x do dec(j);

if i<=j then
begin
tam:=b[k,i];
b[k,i]:=b[k,j];
b[k,j]:=tam;
inc(i);
dec(j);
end;
until i>j;
if iif j>l then qsort(k,l,j);
end;
function timmax(i,x:longint):longint;
var dau,cuoi,k1:longint;
begin
dau:=1;
cuoi:=m;
while dau<=cuoi do
begin
k1:=(dau + cuoi) div 2;
if b[i+1,k1]=x then exit (x);
17


if b[i+1,k1]else
begin
cuoi:=k1-1;
timmax:=b[i+1,k1];
end;

end;

end;
function timmin(i,x:longint):longint;
var dau,cuoi,k1:longint;
begin
dau:=1;
cuoi:=m;
while dau<=cuoi do
begin
k1:=(dau + cuoi) div 2;
if b[i+1,k1]=x then exit (x);
if b[i+1,k1]>x then cuoi:=k1-1
else
begin
dau:=k1+1;
timmin:=b[i+1,k1];
end;
end;
end;
function min(x,y:longint):longint;
begin
if x<=y then min:=x
else min:=y;
end;
begin
doc;
for i:=1 to n do qsort(i,1,m);
cl:=high(longint);
for i:=1 to n-1 do

begin
for j:=1 to m do
begin
max1:=timmax(i,b[i,j]);
min1:=timmin(i,b[i,j]);
cl:=min(cl,abs(max1-b[i,j]));
cl:=min(cl,abs(b[i,j]-min1));
end;
end;
writeln(cl);
readln;
end.
18


Độ phức tạp: O(N×M×logM)
Bài 2. Có hộp N kẹo và mỗi hộp chứa A[i] cái kẹo. Số thứ tự của các viên kẹo được
đánh số từ 1,2,3,..đến hết. Cho biết thứ tự của viên kẹo, hãy chỉ ra viên kẹo đó thuộc hộp
kẹo thứ mấy.
Dữ liệu vào: Tệp ‘Thutukeo.inp’
- Dòng đầu tiên sẽ chứa N (Số hộp).
- Dòng tiếp theo cho biết số kẹo trong hộp thứ i.
- Dòng tiếp theo sẽ chứa Q (Số lần test).
- Q dòng tiếp theo mỗi dòng chứa 1 giá trị nguyên là thứ tự của viên kẹo.
Kết quả: Tệp ‘Thutukeo.out’:
- Chứa Q dòng. In ra hộp kẹo tương ứng với thứ tự viên kẹo, mỗi kết quả trên 1
dòng
Giới hạn: 1≤N,Q≤ 105 ;
‘Thutukeo.inp’
2

23
2
2
4

1≤ A[i] ≤ 106

‘Thutukeo.out’
1
2

Ghi chú
Hộp đầu tiên sẽ có các viên kẹo thứ tự: 1, 2
Hộp thứ hai sẽ có các viên kẹo chỉ số: 3, 4, 5

Ý tưởng: Xây dựng mảng S với ý nghĩa S[i] là tổng các phần tử từ A[1] đến A[i].
Thực hiện tìm kiếm nhị phân trên mảng S.
program chiso_keo;
const nmax=100000;
fi='thutukeo.inp';
fo='thutukeo.out';
var a:array[1..nmax] of longint;
n,q,x:longint;
s:array[0..nmax] of int64;
f,f1:text;
function tknp(x:longint):longint;
var d,c,k:longint;
begin
d:=1;
c:=n;

while d<=c do
begin
k:=(d+c) div 2;
if xbegin
tknp:=1;
exit;
end;
19


end;
end;

if s[k]=x then
begin
tknp:=k;
exit;
end;
if (s[k]>x) and (s[k-1]begin
tknp:=k;
exit;
end
else
if s[k]else c:=k-1;

procedure doc_xuly;
var i:longint;

begin
assign(f1,fo);
rewrite(f1);
assign(f,fi);
reset(f);
readln(f,n);
for i:=1 to n do
begin
read(f,a[i]);
s[i]:=s[i-1]+a[i];
end;
readln(f);
readln(f,q);
for i:=1 to q do
begin
readln(f,x);
writeln(f1,tknp(x));
end;
close(f1);
end;
begin
s[0]:=0;
doc_xuly;
end.
Độ phức tạp: O(N+Q*log(N))
Bài 3. Cho N điểm nằm trên mặt phẳng, điểm thứ i nằm ở tọa độ (xi, yi), bạn cần
phải trả lời q truy vấn.Trong lần truy vấn thứ i, bạn sẽ được cho một số nguyên ri, và xem
bạn vẽ một vòng tròn trung tâm tại gốc (0,0) với bán kính ri, hãy cho biết số điểm nằm
20



trong hoặc trên chu vi của vòng tròn này. Đối với mỗi truy vấn, bạn cần in câu trả lời trên
một dòng.
Tệp dữ liệu vào: ‘sodiem.inp’
- Dòng đầu tiên chứa một số nguyên N là số điểm nằm trên mặt phẳng.
- N dòng tiếp theo mỗi dòng chứa 2 số nguyên cách nhau bởi dấu cách xi và yi, biểu
diễn tọa độ của điểm.
- Dòng tiếp theo chứa một số nguyên q là số lượng truy vấn.
- Q dòng cuối, mỗi dòng chứa một số nguyên ri
Tệp dữ liệu ra: ‘sodiem.out’
Đối với mỗi truy vấn, in câu trả lời trên một dòng.
Giới hạn: 1≤N≤105; -109≤xi, yi≤109; 1≤q≤105; 0≤ri≤1018
Sodiem.inp
5
1
2
3
-1 -1
44
2
3
32

Sodiem.out
3
5

Ý tưởng: Đọc dữ liệu từ tệp, tính khoảng cách từ cách điểm tới gốc tọa độ và lưu
vào mảng P. Sử dụng thuật toán Qsort để sắp xếp mảng P theo thứ tự tăng dần. Sau đó áp
dụng “chặt nhị phân” để tìm số điểm nằm trong và trên đường tròn.

program sodiem;
const
fi='sodiem.inp';
fo='sodiem.out';
nmax=100000;
var
f,f1:text;
n,t,i,q:longint;
b:array[1..nmax] of int64;
p:array[1..nmax] of real;
function tknp(x:int64):int64;
var d,c,k:longint;
begin
d:=1;
c:=n;
if p[n]<=x then
begin
tknp:=n;
21


exit;
end;
while d<=c do
begin
k:=(d+c) div 2;
if p[k]<=sqrt(x*x) then d:=k+1
else
c:=k-1;
end;

tknp:=d;
end;
procedure qsort(l,h:longint);
var i,j:longint;
khoa,tam:real;
begin
i:=l;
j:=h;
khoa:=p[(l+h) div 2];
repeat
while p[i]while p[j]>khoa do dec(j);
if i<=j then
begin
tam:=p[i];
p[i]:=p[j];
p[j]:=tam;
inc(i);
dec(j);
end;
until i>j;
if iif j>l then qsort(l,j);
end;
procedure doc;
var i,x,y:longint;
begin
assign(f,fi);
reset(f);
readln(f,n);

for i:=1 to n do
begin
readln(f,x,y);
p[i]:=sqrt(x*x+y*y);
end;
readln(f,q);
for i:=1 to q do
begin
22


readln(f,t);
b[i]:=t;
end;
end;
begin
assign(f1,fo);
rewrite(f1);
doc;
qsort(1,n);
for i:=1 to q do writeln(f1,tknp(b[i]));
close(f1);
end.
Bài 4. Cho 1 xâu S chỉ chứa các ký tự in thường ['a'-'z'] và 1 số nguyên K. Hãy tìm
các xâu con liên tiếp của S có trọng lượng bằng K.
Trọng lượng của 1 ký tự được tính như sau: ['a']=1; ['b']=2; ['c']=3,…['z']=26.
Trọng lượng của xâu S được tính là tổng trọng lượng các ký tự có trong xâu S.
Dữ liệu đầu vào: Tệp ‘timxaucon.inp’:
Dòng đầu tiên chứa số lượng các trường hợp cần kiểm tra T. Với mỗi test dữ liệu
được cho trên 2 dòng, dòng đầu là số nguyên K và dòng sau là xâu S.

Dữ liệu ra: Tệp ‘timxaucon.out’
In ra số lượng các xâu con có trọng lượng bằng K, mỗi số trên 1 dòng.
Giới hạn:1<=T<=20; 1<=K<=26*|S| ; 1<=|S|<=1000000
Timxaucon.inp
2
5
abcdef
4
abcdef

Timxaucon.out
2
1

Chương trình:
program timxaucon;
const fi='timxaucon.inp';
fo='timxaucon.out';
nmax=20;
var
f,f1:text;
luus:array[1..nmax] of ansistring;
s1,cs:array[0..nmax] of int64;
i,j,t,d,n:longint;
procedure doc;
var i:longint;
s:ansistring;
begin
assign(f,fi);
23



reset(f);
readln(f,t);
for i:=1 to t do
begin
readln(f,cs[i]);
readln(f,luus[i]);
end;
end;
procedure taobang(s:ansistring);
var i,x:longint;
begin
s1[0]:=0;
for i:=1 to length(s) do
begin
x:=ord(s[i])-96;
s1[i]:=s1[i-1]+x;
end;
end;
function tknp(x:longint):boolean;
var d,c,k:longint;
begin
tknp:=false;
d:=1;
c:=n;
while d<=c do
begin
k:=(d+c) div 2;
if s1[k]=x then

begin
tknp:=true;
exit;
end;
if s1[k]>x then c:=k-1
else d:=k+1;
end;
end;
begin
doc;
assign(f1,fo);
rewrite(f1);
for i:=1 to t do
begin
taobang(luus[i]);
n:=length(luus[i]);
d:=0;
for j:=0 to length(luus[i]) do
if tknp(s1[j]+cs[i]) then inc(d);
writeln(f1,d);
24


end;
close(f1);
end.
Bài 5. Trong một chuyến đi đến siêu thị CoopMart, Tân muốn mua một số món
hàng cho bạn bè và người thân của mình.
Siêu thị này có một số quy tắc kỳ lạ. Nó chứa n các sản phẩm khác nhau được đánh
số từ 1 đến n. Mặt hàng thứ i có giá cơ bản không đổi là bi. Nếu Tân mua K mặt hàng với

chỉ số x1, x2, ..., xk, thì giá của sản phẩm xj là bxj + xj* k với 1 ≤ j ≤ k. Nói cách khác,
giá của một mặt hàng bằng với giá căn bản cộng với chỉ số nhân với hệ số k.
Tân muốn mua càng nhiều đồ càng tốt mà không phải trả nhiều tiền hơn một số SP.
Lưu ý rằng mỗi món hàng chỉ được mua 1 lần. Hãy giúp Tân chọn cách mua với nhiều
mặt hàng nhất.
Dữ liệu vào: Tệp ‘muahang.inp’
- Dòng đầu tiên chứa hai số nguyên n và SP (1 ≤ n ≤ 105 và 1 ≤ SP ≤ 109) - số lượng
các mặt hàng trên và số tiền tối đa Tân có thể mua
- Dòng thứ hai chứa n số nguyên b1, b2, ..., bn(1 ≤ bi ≤ 105) - giá trị cơ bản của các
mặt hàng.
Dữ liệu ra: Tệp ‘muahang.out’
- In hai số nguyên k, T là số lượng tối đa các hàng mà Tân có thể mua và tổng chi
phí tối thiểu để mua những mặt hàng k.
Muahang.inp
3 11
235

Muahang.out
2 11

Giải thích test
Trong ví dụ này, Tân không thể lấy cả ba mặt hàng
bởi vì giá của mỗi mặt hàng lần lượt là [5, 9, 14] và
tổng chi phí của nó 28. Nếu Tân chỉ mua hai mặt
hàng, thì chi phí sẽ là [4, 7] và tổng chi phí là 11.

program muahang;
const fi='muahang.inp';
fo='muahang.out';
nmax=10000;

var
f:text;
a,s:array[1..nmax] of int64;
n,sp,cs,kq:longint;
procedure qsort(l,h:integer);
var i,j,tam,k:longint;
begin
i:=l;
j:=h;
k:=a[(l+h) div 2];
repeat
while a[i]25