You are on page 1of 21

Thuật toán tìm kiếm nhị phân

Bài toán: Cho dãy số nguyên 𝑨 gồm 𝒏 số nguyên 𝒂𝟏, 𝒂𝟐, … , 𝒂𝒏(𝟏 ≤ 𝒏 ≤
𝟏𝟎𝟔, |𝒂𝒊| ≤ 𝟏𝟎𝟗) đã sắp xếp theo thứ tự không giảm và cho số nguyên 𝒌 (khóa
tìm kiếm).

Yêu cầu: Cho biết số nguyên 𝒌 có xuất hiện trong dãy 𝑨 hay không, nếu tìm
thấy 𝒌 cho biết vị trí số đó trong dãy?

Thuật toán cho bài toán này:


l=1;r=n;
while (l<=r)
{
int m=(l+r)/2;
if (a[m]==k)
{
cout<<k<<”xuat hien trong day o vi tri”<<m;
break;
}
else
if (a[m]>k) r=m-1;
else l=m+1;
}
if (l>r) cout<<k<<”khong xuat hien trong day”;

Thuật toán tìm kiếm nhị phân được mô tả khi các phần tử của mảng được
sắp xếp, thuật toán luôn lấy phần tử ở giữa trong phạm vi tìm kiếm để so sánh
với khóa tìm kiếm. Nếu phần tử ở giữa bằng với khóa tìm kiếm thì thuật toán sẽ
dừng lại và trả về kết quả là tìm thấy. Nếu phần tử ở giữa lớn hơn khóa tìm kiếm
thì quá trình tìm kiếm tiếp tục nửa đầu của phạm vi tìm kiếm. Nếu phần tử ở
giữa nhỏ hơn khóa tìm kiếm thì quá trình tìm kiếm sẽ tiếp tục ở nửa sau của
phạm vi tìm kiếm. Như vậy, mỗi lần lặp thuật toán có thể loại bỏ được một nửa
số phần tử trong phạm vi tìm kiếm mà chắc chắn khóa tìm kiếm sẽ không xuất
hiện.

Đây là thuật toán tìm kiếm rất nhanh, hiệu quả và số lần lặp để trả lời
khóa tìm kiếm có xuất hiện hay không rất nhỏ. Cụ thể, mỗi lần lặp nếu chưa tìm
thấy khóa tìm kiếm, thuật toán loại đi được một nửa dãy (do so sánh khóa tìm
Trang 1
kiếm với phần tử ở giữa dãy). Độ phức tạp của thuật toán là 𝑂(log 𝑛). Ví dụ, với
kích thước cho trong bài toán trên thì trong trường hợp xấu nhất là không tìm
thấy khóa chỉ cần tối đa khoảng 20 lần lặp.

Thuật toán trên nếu trong dãy có nhiều số bằng 𝑘 thì kết quả trả về là vị trí
bất kì tìm được.
Thuật toán cải tiến thuật toán tìm kiếm nhị phân để khi tìm được khóa tìm kiếm
𝑘 thì luôn trả về vị trí đầu tiên hoặc vị trí cuối cùng tìm được khóa ở trong dãy.

a. Tìm kiếm trả về vị trí đầu tiên khi tìm thấy khóa

Bài toán:Cho dãy số nguyên 𝑨 gồm 𝒏 số nguyên 𝒂𝟏 , 𝒂𝟐, … , 𝒂𝒏(𝟏 ≤ 𝒏 ≤
𝟏𝟎𝟔, |𝒂𝒊| ≤ 𝟏𝟎𝟗) đã sắp xếp theo thứ tự không giảm và cho số nguyên 𝒌
(khóa tìm kiếm).

Yêu cầu: Cho biết số nguyên 𝒌 có xuất hiện trong dãy 𝑨 hay không, nếu tìm
thấy 𝒌 cho biết vị trí đầu tiên tìm được?

Từ thuật toán đưa ra ở mục 1, ta cải tiến như sau:


l=1;r=n;
while (l<r)
{
int m=(l+r)/2;
if (a[m]==k) r=m;
else
if (a[m]>k) r=m-1;
else l=m+1;
}
if (a[l]==k)
cout<<k<<”xuat hien trong day o vi tri”<<l;
else
cout<<k<<”khong xuat hien trong day”;

- Cải tiến đầu tiên là ở câu lệnh lặp while, ta còn đi tìm khóa 𝑘 nếu phạm vi
tìm kiếm nhiều hơn một phần tử (l<r).

- Cải tiến thứ hai, khi 𝑎[𝑚] == 𝑘 ta vẫn sẽ tiếp tục tìm kiếm bằng cách
thu hẹp phạm vi tìm kiếm về phần đầu bằng cách gán 𝑟 = 𝑚. Như vậy,
phạm vi tìm kiếm mới vẫn đảm bảo chắc chắn khóa 𝑘 tồn tại trong kết

Trang 2
quả tìm kiếm cuối cùng.

Khi câu lệnh while kết thúc, tức là khi 𝑙 = 𝑟. Phạm vi tìm kiếm chỉ còn một phần
tử trong dãy, thì công việc còn lại rất đơn giản. Ta kiểm tra nếu 𝑎[𝑙] == 𝑘 thì 𝑙
chính là vị trí đầu tiên tìm được, ngược lại thì kết quả là không tìm thấy.

Như vậy, ta đã cải tiến thuật toán tìm kiếm nhị phân ban đầu để đáp ứng yêu cầu
trả về vị trí đầu tiên tìm thấy. Độ phức tạp của thuật toán sau khi cải tiến vẫn
không bị thay đổi 𝑂(log 𝑛).

Thuật toán tìm kiếm nhị phân vừa cải tiến còn có thể viết gọn hơn như sau:
l=1;r=n;
while (l<r)
{
int m=(l+r)/2;
if (a[m]>=k) r=m;
else l=m+1;
}
if (a[l]==k)
cout<<k<<”xuat hien trong day o vi tri”<<l;
else
cout<<k<<”khong xuat hien trong day”;
Trong cách viết gọn này, ta gộp hai điều kiện 𝑎[𝑚] == 𝑘 và 𝑎[𝑚] > 𝑘 vào
thành một điều kiện 𝑎[𝑚] ≥ 𝑘 và gộp hai lệnh 𝑟 = 𝑚 − 1 và 𝑟 = 𝑚 thàng
một lệnh 𝑟 = 𝑚. Câu hỏi đặt ra là: tại sao ta lại có thể gộp được như vậy?
Câu trả lời đơn giản như sau, trong câu lệnh:

if (a[m]>k) r=m-1;

Nếu chúng ta thay phạm vi 𝑟 = 𝑚 − 1 thành 𝑟 = 𝑚 thì phạm vi tìm kiếm của
chúng ta chỉ thêm một phần tử.Điều này không ảnh hưởng đến kết quả bài toán, đó
là cơ sở để có thể viết gọn lại cách cài đặt thuật toán.

b. Tìm kiếm trả về vị trí cuối cùng khi tìm thấy khóa

Bài toán:Cho dãy số nguyên 𝑨 gồm 𝒏 số nguyên 𝒂𝟏 , 𝒂𝟐, … , 𝒂𝒏(𝟏 ≤ 𝒏 ≤
𝟏𝟎𝟔, |𝒂𝒊| ≤ 𝟏𝟎𝟗) đã sắp xếp theo thứ tự không giảm và cho số nguyên 𝒌
(khóa tìm kiếm).

Trang 3
Yêu cầu: Cho biết số nguyên 𝒌 có xuất hiện trong dãy 𝑨 hay không, nếu tìm
thấy 𝒌 cho biết vị trí cuối cùng tìm được?

Từ thuật toán ở mục 2.a, ta cải tiến như sau:


l=1;r=n;
while (l<r)
{
int m=(l+r+1)/2;
if (a[m]<=k) l=m;
else r=m-1;
}
if (a[l]==k)
cout<<k<<”xuat hien trong day o vi tri”<<l;
else
cout<<k<<”khong xuat hien trong day”;

- Cải tiến dễ nhất và trực quan nhất đó là nếu 𝑎[𝑚] ≤ 𝑘 thì khóa 𝑘 nằm ở
nửa sau của dãy tìm kiếm, ta chỉ việc thu nhỏ phạm vi tìm kiếm bằng cách
gán 𝑙 = 𝑚. Ngược lại thì khóa 𝑘 nằm ở nửa đầu của dãy tìm kiếm nên ta
gán 𝑟 = 𝑚 − 1.

- Cải tiến thứ hai cũng là cải tiến quan trọng nhất đó là lệnh

int m=(l+r+1)/2;

Câu hỏi đặt ra đầu tiên khi thấy lệnh này đó là tại sao phải cộng 1 sau đó
mới chia 2. Để hiểu rõ hơn về lệnh này chúng tôi đưa ra một ví dụ như sau:

Cho dãy 𝐴 gồm 𝑛 = 2 phần tử 𝑎[1] = 2; 𝑎[2] = 5, khóa tìm kiếm 𝑘 = 5. Nếu

ta để lệnh:

int m=(l+r)/2;

Với𝑙 = 1, 𝑟 = 2 thì 𝑚 luôn có giá trị = 1 và điều kiện 𝑎[𝑚] ≤ 𝑘 luôn đúng. Như
vậy giá trị 𝑙, 𝑟 không đổi, dẫn đến lệnh lặp 𝑤ℎ𝑖𝑙𝑒 không bao giờ kết thúc. Vấn
đề này được giải thích như sau: câu lệnh int m=(l+r)/2;làm việctrên các biến số
nguyên nên khi ta chia cho 2 kết quả luôn luôn làm tròn xuống. Trong khi phần
này ta cần giải quyết vấn đề tìm vị trí cuối cùng mà khóa xuất hiện. Tức là, ta
cần tiến về phía cuối dãy để tìm 𝑘 thỏa mãn. Vậy nên ta phải làm tròn lên khi

Trang 4
chia cho 2, đó là lí do vì sao ta phải cộng thêm 1.

Với cải tiến cho thuật toán tìm kiếm nhị phân áp dụng trong trường hợp này thì
độ phức tạp không thay đổi: 𝑂(log 𝑛)

Tiếp theo chúng tôi sẽ giới thiệu một số bài toán để thấy rõ hơn ứng dụng của
cải tiến này.

c. Một số ví dụ áp dụng


Vi ́ du ̣ 1: TỔNG CẶP SỐ
Xét dãy số nguyên dương khác nhau từng đôi một a1, a2, . . . an, trong đó 1 ≤ ai
≤ 109, 1 ≤ n ≤ 105).
Yêu cầu: Với số nguyên x cho trước (1 ≤ 𝒙 ≤ 109) hãy xác định số cặp (ai,
aj) thỏa mãn các điều kiện:
 ai + aj = x,
 1 ≤ i<j ≤ n.
Dữ liệu: Vào từ file văn bản SUMX.INP:
 Dòng đầu tiên chứa số nguyên n,
 Dòng thứ 2 chứa n số nguyên a1, a2, . . . an,
 Dòng thứ 3 xhứa số nguyên x.
Kết quả: Đưa ra file văn bản SUMX.OUT một số nguyên – số cặp tìm được.
Ví dụ:
SUMX.INP SUMX.OUT
9 3
5 12 7 10 9 1 2 3 11
13

Hướng dẫn:

- Sắp xếp dãy số theo thứ tự không giảm.

- Duyệt lần lượt các số trong dãy, với mỗi số ở vị trí 𝑖 ta cần tìm số số trong
dãy các số 𝑎𝑖+1, … , 𝑎𝑛 có giá trị bằng 𝑥 − 𝑎𝑖 .

- Để tìm số số có giá trị bằng 𝑥 − 𝑎𝑖 trong dãy 𝑎 từ [𝑖 + 1, 𝑛] ta sử dụng


thuật toán tìm kiếm nhị phân:

o Tìm vị trí 𝑙 bé nhất có 𝑎[𝑙] = 𝑥 − 𝑎𝑖

o Tìm vị trí 𝑟 lớn nhất có 𝑎[𝑟] = 𝑥 − 𝑎𝑖


Trang 5
o Số số cần tìm là 𝑟 − 𝑙 + 1

- Cộng tổng tất cả số số thỏa mãn ở từng vị trí 𝑖 ta thu được kết quả đáp ứng
yêu cầu của bài.

Chương trình:
#include <bits/stdc++.h>

using namespace std;

int n, a[100005],x;
long long res=0;

int tim_vi_tri_nho_nhat(int i, int j,int key)


{
int l=i,r=j;
while (l<r)
{
int m=(l+r)/2;
if (a[m]>=key) r=m;
else l=m+1;

Trang 6
}
if (a[l]==key) return l;
return -1;
}
int tim_vi_tri_lon_nhat(int i, int j, int key)
{
int l=i,r=j;
while (l<r)
{
int m=(l+r+1)/2;
if (a[m]<=key) l=m;
else r=m-1;
}
if (a[l]==key) return l;
return -1;
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
freopen("sumx.inp","r",stdin);
freopen("sumx.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>x;
sort(a+1,a+n+1);
for(int i=1;i<n;i++)
{
int left=tim_vi_tri_nho_nhat(i+1,n,x-a[i]);
if (left<0) continue;
int right=tim_vi_tri_lon_nhat(i+1,n,x-a[i]);
res+=right-left+1ll;
}
cout<<res;
return 0;
}
Vi ́ du ̣ 2:Trò chơi với dãy số (VOI2008)

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à:

𝑎1, 𝑎2, . . . , 𝑎𝑛

còn dãy số mà bạn thứ hai chọn là

Trang 7
𝑏1, 𝑏2, . . . , 𝑏𝑛

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 ai (1 ≤ i ≤ n), còn bạn thứ hai đưa ra số hạng bj (1 ≤ j ≤ n) thì
giá của lượt chơi đó sẽ là |ai+bj|.

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).

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ể.

Dữ liệu: Cho trong tệp AGAME.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 a1, a2, ..., an (|ai| ≤ 109, i=1, 2, ..., n)

 Dòng thứ hai chứa dãy số nguyên b1, b2, ..., bn (|bi| ≤ 109, i=1, 2, ..., n)

Hai số liên tiếp trên một dòng được ghi cách nhau bởi dấu cách.

Kết quảđưa ra tệp AGAME.OUTmột số duy nhất là giá nhỏ nhất tìm được. Ví

dụ:
AGAME.INP AGAME.OUT
2 0
1 -2
23
Hướng dẫn:

- Sắp xếp dãy 𝑎 theo thứ tự không giảm.


- Duyệt lần lượt từng phần tử của dãy 𝑏, với mỗi phần tử 𝑏𝑖
o tìm phần tử có chỉ số lớn nhất 𝑗 trên dãy 𝑎 sao cho 𝑎𝑗 + 𝑏𝑖 ≤ 0 (sử
dụng thuật toán tìm kiếm nhị phân)

Trang 8
o Kết quả tốt nhất khi chọn 𝑏𝑖 là min (|𝑏𝑖 + 𝑎𝑗|, |𝑏𝑖 + 𝑎𝑗+1|)
- Kết quả bài toán là giá trị nhỏ nhất trong các lượt chọn 𝑏𝑖

Chương trình:
#include <bits/stdc++.h> using

namespace std;

const int N=1e5+5; int n,


a[N],b[N],res;
int tim_vi_tri_lon_nhat(int i, int j,int key)
{
int l=i,r=j; while
(l<r)
{
int m=(l+r+1)/2;
if (a[m]+key<=0) l=m; else
r=m-1;
}
return l;
}

int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
freopen("agame.inp","r",stdin);
freopen("agame.out","w",stdout); cin>>n;
for(int i=1;i<=n;i++) cin>>a[i]; for(int
i=1;i<=n;i++) cin>>b[i]; sort(a+1,a+n+1);
sort(b+1,b+n+1);
res=2e9+5;
for(int i=1;i<=n;i++)
{
int j=tim_vi_tri_lon_nhat(1,n,b[i]);
res=min(res,abs(a[j]+b[i]));
if (j<n) res=min(res,abs(a[j+1]+b[i]));
}
cout<<res;
return 0;
}
Ví dụ 3.Trò chơi trên dãy số (DH2018)

Trang 9
Hai bạn A và B chơi trò chơi trên hai dãy số như sau: A sẽ tạo ra hai dãy số
nguyên 𝑥1, 𝑥2, … , 𝑥𝑚 và 𝑦1, 𝑦2, … , 𝑦𝑛. Sau đó, B sẽ chọn một số nguyên 𝑠 và
yêu cầu A tìm một số thuộc dãy thứ nhất và một số thuộc dãy thứ hai sao cho
tổng
hai số được chọn chênh lệch với 𝑠 là nhỏ nhất.

Yêu cầu: Cho hai dãy số nguyên 𝑥1, 𝑥2, … , 𝑥𝑚 và 𝑦1, 𝑦2, … , 𝑦𝑛 mà A tạo ra, cho
𝑠1 , 𝑠2 , … , 𝑠𝑘 là 𝑘 câu hỏi của 𝐵. Với câu hỏi 𝑠𝑖 (𝑖 = 1,2, … , 𝑘) đưa ra giá trị chênh
lệch nhỏ nhất của 𝑠𝑖 với tổng hai số tìm được.

Dữ liệu: Vào từ file văn bản SEQGAME.INP:


- Dòng đầu chứa ba số nguyên dương 𝑚, 𝑛, 𝑘 (𝑚, 𝑛 ≤ 105 , 𝑘 ≤ 10);
- Dòng thứ hai chứa 𝑚 số nguyên 𝑥1, 𝑥2, … , 𝑥𝑚 (|𝑥𝑖| ≤ 109);
- Dòng thứ ba chứa 𝑛 số nguyên 𝑦1, 𝑦2, … , 𝑦𝑛 (|𝑦𝑖| ≤ 109);
- Dòng thứ tư chứa 𝑘 số nguyên 𝑠1, 𝑠2, … , 𝑠𝑘 (|𝑠𝑖| ≤ 109).

Kết quả: Ghi ra file văn bản SEQGAME.OUTgồm 𝑘 dòng, dòng thứ 𝑖 ghi giá trị
chênh lệch nhỏ nhất của 𝑠𝑖 với tổng hai số tìm được.

Ví dụ:
SEQGAME.INP SEQGAME.OUT
342 0
132 1
-1 5 3 1
29
Hướng dẫn:

- Đây là bài tổng quát hơn bài đã cho trong ví dụ 2, thay vì tìm tổng 𝑎𝑖 + 𝑏𝑗
chệch lệch với 0 nhỏ nhất thì bài này yêu cầu tìm tổng 𝑎𝑖 + 𝑏𝑗 chệch lệch
với 𝑠 (là số cho trước) nhỏ nhất.

- Chúng ta sửa chương trình đã cho ở ví dụ 2 là đáp ứng yêu cầu bài toán.

Chương trình:
#include <bits/stdc++.h>

using namespace std;


Trang 10
const int N=1e5+5;
int n,m,k,s,a[N],b[N],res;
int tim_vi_tri_lon_nhat(int i, int j,int key)
{
int l=i,r=j; while
(l<r)
{
int m=(l+r+1)/2;
if (a[m]+key<=s) l=m; else
r=m-1;
}
return l;
}

int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
freopen("seqgame.inp","r",stdin);
freopen("seqgame.out","w",stdout);
cin>>m>>n>>k;
for(int i=1;i<=m;i++) cin>>a[i]; for(int
i=1;i<=n;i++) cin>>b[i]; sort(a+1,a+m+1);
sort(b+1,b+n+1);
while (k--)
{
cin>>s;
res=2e9+5;
for(int i=1;i<=n;i++)
{
int j=tim_vi_tri_lon_nhat(1,m,b[i]);
res=min(res,abs(a[j]+b[i]-s));
if (j<m) res=min(res,abs(a[j+1]+b[i]-s));
}
cout<<res<<'\n';
}
return 0;
}
Ví dụ 4:HÀNG CÂY

Trong khu vườn, người ta trồng một hàng cây chạy dài gồm có N cây, mỗi cây
có độ cao là a1, a2,…aN.

Trang 11
Người ta cần lấy M mét gỗ bằng cách đặt cưa máy sao cho lưỡi cưa ở độ cao H
(mét) để cưa tất cả các cây có độ cao lớn hơn H (dĩ nhiên những cây có độ cao
không lớn hơn H thì không bị cưa).

Ví dụ: Nếu hàng cây có các cây với độ cao tương ứng là 20; 15; 10 và 18 mét,
cần lấy 7 mét gỗ. Lưỡi cưa đặt tại độ cao hợp lí là 15 mét thì độ cao của các cây
còn lại sau khi bị cưa tương ứng là 15; 15; 10 và 15 mét. Tổng số mét gỗ lấy
được là 8 mét (dư 1 mét).

Yêu cầu: Hãy tìm vị trí đặt lưỡi cưa hợp lí (số nguyên H lớn nhất) sao cho lấy
được M mét gỗ và số mét gỗ dư ra là ít nhất.

Dữ liệu: Vào từ tệp văn bản WOOD.INP

 Dòng thứ nhất chứa 2 số nguyên dương N và M (1≤N≤106; 1≤M≤2x109) cách

nhau một dấu cách.

 Dòng thứ hai chứa N số nguyên dương ai là độ cao của mỗi cây trong hàng

(1≤ai≤109; i=1…N), mỗi số cách nhau ít nhất một dấu cách.

Kết quả: Đưa ra tệp WOOD.OUT là một số nguyên cho biết giá trị cần tìm.

Ví dụ:
WOOD.INP WOOD.OUT
47 15
20 15 10 18
Hướng dẫn:

- Bài toán này cho ta thấy một ứng dụng rất hay khi sử dụng thuật toán tìm
kiếm nhị phân để giải quyết.

- Phạm vi tìm kiếm để tìm ra lời giải tối ưu cho bài toán là thử các giá trị
𝐻 = 0, 1, 2, … , 𝑚𝑎𝑥(𝑎𝑖 )

- Nhận xét: Nếu ta đặt cưa máy ở độ cao 𝐻 lấy được 𝑀 mét gỗ, thì khi
đặt cưa máy ở vị trí 𝐻 − 1chắc chắn ta lấy được 𝑀 mét gỗ. Đó là cơ
sở để chúng ta có thể áp dụng thuật toán tìm kiếm nhị phân theo độ
cao 𝐻.

Trang 12
- Theo yêu cầu bài toán thì ta chỉ cần tìm vị trí 𝐻 lớn nhất thỏa mãn lấy 𝑀
mét gỗ.

Chương trình:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n;
ll m,h[1000005];
bool ok(int x)
{
ll s=0;
for(int i=1;i<=n;i++)
if (h[i]>x) s+=h[i]-x;
if (s>=m) return true;
return false;
}
int main()
{
freopen("wood.inp","r",stdin);
freopen("wood.out","w",stdout);
scanf("%d %lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&h[i]);
int l=0, r=1e9,mid;
while (l<r)
{
mid=(l+r+1)/2;
if (ok(mid)) l=mid;
else r=mid-1;
}
printf("%d",l);
return 0;
}

Ở chương trình trên ta thấy, việc kiểm tra giá trị ở giữa (mid) không còn
đơn giản. Để kiểm tra giá trị 𝑚𝑖𝑑 ta cần viết một chương trình con để xác
định xem nó có thỏa mãn điều kiện bài yêu cầu.

Vi ́ du ̣ 5:Đi công tác nước ngoài (HSG 12 chuyên)

Một đoàn gồm có m người đi công tác nước ngoài. Họ đang xếp hàng chờ
đăng kí xuất cảnh tại sân bay. Có n bàn để đăng kí phục vụ hành khách. Mỗi bàn
đăng kí, nhân viên làm việc với sự hiệu quả khác nhau dẫn đến thời gian phục
Trang 13
vụ cho một hành khách ở mỗi bàn là khác nhau. Tại bàn thứ k, để làm thủ tục
đăng kí cho một hành khách mất Tk giây.

Ban đầu, tất cả các bàn đều sẵn sàng phục vụ hành khách và các thành
viên trong đoàn lần lượt đăng kí từng người một. Một người chỉ có thể làm thủ
tục đăng kí tại một bàn nếu như một bàn nào đó đã sẵn sàng phục vụ. Tại một
thời điểm, một hành khách có thể ngay lập tức đến một bàn để đăng kí nếu bàn
đó không phục vụ ai nhưng cũng có thể đợi một bàn phục vụ khác làm thủ tục
nhanh hơn. Các thành viên trong đoàn đều là những người rất giỏi về tin học, họ
đưa ra quyết định lựa chọn bàn phục vụ rất nhanh để khi tất cả các thành viên
trong đoàn hoàn thành thủ tục đăng kí với thời gian ít nhất có thể.

Ví dụ: Có hai bàn phục vụ đăng kí với thời gian phục vụ mỗi bàn lần lượt
là 7 và 10 giây. Có 6 người trong đoàn, hai người đầu tiên ngay lập tức đến 2
bàn đăng kí. Sau 7 giây, bàn 1 rỗi và người thứ 3 vào bàn 1 làm thủ tục đăng kí.
Sau 10 giây, người 4 làm thủ tục đăng kí bàn 2. Sau 14 giây, người 5 vào bàn 1
làm thủ tục đăng kí. Sau 20 giây bàn 2 đang rỗi nhưng người 6 quyết định đợi
thêm 1 giây để sau giây thứ 21 bàn 1 rỗi và làm thủ tục đăng kí bàn này. Như
vậy, thời gian làm thủ tục đăng kí cho 6 người hoàn thành trong 28 giây. Nếu 6
người này không đợi bàn phục vụ đăng kí nhanh thì tổng thời gian hoàn thành
đăng kí mất 30 giây.

Yêu cầu: bạn hãy viết chương trình tìm thời gian ít nhất để phục vụ đăng kí M
người.

Dữ liệu vào cho trong tệp CHECKIN.INP

- dòng 1 chứa hai số nguyên dương N (1≤N≤105) và M (1≤M≤109) tương ứng là


số bàn phục vụ đăng kí và số thành viên trong đoàn công tác.

- Mỗi dòng trong N dòng sau, dòng thứ k chứa một số nguyên Tk là thời gian
phục vụ đăng kí cho một hành khách ở bàn k (1≤Tk≤109).

Kết quả đưa ra tệp CHECKIN.OUT một số duy nhất là thời gian ít nhất tìm
được.

Trang 14
Ví dụ
CHECKIN.INP CHECKIN.OUT
26 28
7
10
Hướng dẫn:

- Giả sử để phục vụ đăng kí cho M người mất thời gian là T.
- Mỗi bàn ta có thể tính số người được phục vụ là: T div Tk
- Tìm T nhỏ nhất thỏa mãn phục vụ được cho M người (sử dụng thuật toán
tìm kiếm nhị phân)
- Độ phức tạp O(NlogM)
Chương trình
#include <bits/stdc++.h>

using namespace std;

typedef long long int64;


int64 n,m,a[100005];

int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
freopen("checkin.inp","r",stdin);
freopen("checkin.out","w",stdout);

cin>>n>>m;
for (long i=1;i<=n;i++) cin>>a[i];

sort(a+1,a+1+n);
int64 l=1,r=a[n]*m;
while (l<r)
{
int64 mid=(l+r)/2;
int64 res=0;
for (long i=1;i<=n;i++) res+=mid/a[i];
if (res>=m)
r=mid;
else
l=mid+1;
Trang 15
}
cout<<l;
return 0;
}
Các ví dụ tiếp theo chúng tôi sẽ đưa ra các bài tập trên trang giải bài trực tuyến
ntucoder.net. Đây là một cách hiệu quả để giúp học sinh nâng cao khả năng tự
học, tự nghiên cứu trong thời đại internet rất phát triển hiện nay.

Khi làm các bài trên trang giải bài trực tuyến, khuôn dạng vào ra luôn là sử
dụng thiết bị vào ra chuẩn:nhập từ bàn phím, in ra màn hình.

Vi ́ du ̣ 6: Sân điền kinh (http://ntucoder.net/Problem/Details/135)

Trong một sân điền kinh, người ta kẻ các vạch sơn để thi đấu các cự ly khác
nhau. Có tất cả n vạch sơn. Vạch sơn thứ i cách đầu sân khoảng cách là ai mét
(a1 < a2 < a3 <...< an). Với một cự ly chạy thi đấu là m mét, Ban tổ chức cần tìm
02 vạch sơn để thi đấu cự ly này. Nghĩa là tìm ra vạch sơn xuất phát ai và vạch
sơn kết thúc aj sao cho aj - ai = m. Bạn hãy giúp ban tổ chức tìm ra hai vạch sơn
này nhé.

Dữ liệu nhập:

- Dòng đầu tiên là hai số nguyên n và m cách nhau một khoảng trắng (1 ≤ n ≤
105, 1 ≤ m ≤ 109).

- Dòng thứ hai là n số nguyên a1, a2, a3,..., an, mỗi số cách nhau một khoảng
trắng. (0 ≤ ai ≤ 109). Dữ liệu đề bài cho đảm bảo a1 < a2 < a3 <...< an.

Dữ liệu xuất:

- Nếu có đáp án, in ra hai số x, y thể hiện hai vạch sân cần tìm (y - x = m). Nếu
có nhiều đáp án, có thể in ra đáp án bất kỳ.

- Nếu không có đáp án, in ra -1.

Ví dụ:
INPUT OUTPUT
42 57
1 5 7 10
Trang 16
Vi ́ du ̣ 7: Khiêu vũ(http://ntucoder.net/Problem/Details/5571)

Câu chuyện tình yêu Elo Cruz và Mara ở Philippines là một minh chứng cho
tình yêu đích thực, không màng đến ngoại hình. Họ khiến cư dân mạng thế giới
phải khâm phục vì một tình yêu bất chấp những khác biệt về ngoại hình. Tuy
nhiên admin rất lo lắng cho Elo, không biết anh chàng này sẽ phải chọn cây ghế
cao thế nào để hôn vợ. Cho nên trong buổi tiệc khiêu vũ “Cơn gió đêm hè!” sắp
đến đây admin muốn các cặp đôi có chiều cao chênh lệch phải đúng bằng K mới
được khiêu vũ cùng nhau.

Yêu cầu:Bạn hãy tính giúp cho admin xem có thể có bao nhiêu cách sắp xếp
từng cặp đôi với nhau thỏa mãn.

Input:

- Dòng đầu tiên là N - số lượng người tham gia bữa tiệc và số K (N≤10^5,
K≤10^9)

- Các dòng tiếp theo là chiều cao của N người tham gia bữa tiệc – không có 2
người nào có chiều cao giống nhau. (Hi ≤ 10^9)

Output:

- Gồm một số duy nhất là số cách lớn nhất có thể sắp xếp.

Ví dụ
INPUT OUTPUT
62 3
132495

Trang 17
Vi ́ du ̣ 8: Thi nấu ăn(http://ntucoder.net/Problem/Details/1177)

Có n bạn sinh viên đang tham gia dự thi nấu ăn nhân dịp năm mới và được đánh
số báo danh từ 1 đến n, bạn sinh viên thứ i tham dự với số lượng là ai món ăn.
Ban tổ chức sẽ đánh số các món ăn dự thi như sau: các món ăn của thí sinh thứ
nhất đánh số từ 1 đến a1, các món ăn của thí sinh thứ hai đánh số từ a1+1 đến
a1+a2.... và tương tự như vậy cho đến món cuối cùng. Sau khi chấm thi, Ban tổ
chức chọn trao giải cho m món ăn với các số hiệu là p1, p2, ..., pm. Hãy cho biết
các món ăn đạt giải đó thuộc về các bạn sinh viên nào?

Dữ liệu nhập: gồm 4 dòng

- Dòng thứ nhất là số nguyên n (1 ≤ n ≤ 105) là số thí sinh tham gia dự thi.
- Dòng thứ hai là n số nguyên a1, a2, ..., an (1 ≤ ai ≤ 104) là số lượng món ăn
của từng thí sinh, mỗi số cách nhau một khoảng trắng.

- Dòng thứ ba là số nguyên m (1 ≤ m ≤ 104) là số lượng món ăn đạt giải.

- Dòng thứ tư là m số nguyên p1, p2, ..., pm là số hiệu của m món ăn đạt giải,
mỗi số cách nhau một khoảng trắng.

Dữ liệu xuất:

- Là m số nguyên s1, s2, ..., sm cho biết số báo danh thí sinh của từng món ăn đạt
giải (món ăn pi là của thí sinh số báo danh si), mỗi số cách nhau một khoảng
trắng.


dụ:
INPUT OUTPUT
5 124
54123
3
5 6 12

Trang 18
Ví dụ 9: Đặt tram phủ sóng (http://ntucoder.net/Problem/Details/4408)

Nhà cung cấp dịch vụ viễn thông Mobi đã khảo sát số lượng người sẽ dùng dịch
vụ trên một con đường thẳng mới được xây dựng và đánh dấu lại những vị trí
trên con đường này. Đầu con đường được đánh tọa độ bắt đầu từ 0. Tại vị trí có
tọa độ X (X nguyên dương) có số lượng người sẽ sử dụng dịch vụ là Y. Trước
mắt, nhà cung cấp dịch vụ cần đặt một trạm phát sóng có bán kính phủ sóng
là K đơn vị chiều dài để phủ sóng cho một số người sử dụng dịch vụ trên con
đường này.

Yêu cầu: Bạn hãy xác định vị trí đặt trạm phát sóng (tọa độ nguyên dương) sao
cho trạm có thể phục vụ được số lượng người sử dụng nhiều nhất có thể.

Dữ liệu:
- Dòng đầu tiên ghi hai số nguyên N và K (0 < N ≤ 106, 0 < K ≤ 2*106), trong
đó N là số điểm dân cư đã được đánh dấu, K là bán kính phủ sóng của trạm.

- Trong N dòng tiếp theo, dòng thứ i (i=1..N) ghi hai số nguyên X và Y cho biết
tại vị trí X có số lượng người dùng là Y (0 ≤ X ≤ 106, 0 ≤ Y ≤104). Các số trên
cùng dòng viết cách nhau ít nhất một dấu cách.

Kết quả: một số nguyên cho biết số người dùng nhiều nhất sẽ được phục vụ.

Ví dụ
INPUT OUTPUT
43 11
74
15 10
22
15

Ví dụ 10: Bảng nhân (http://ntucoder.net/Problem/Details/2242)

Cho một ma trận các số nguyên gồm n dòng m cột. Các dòng được đánh số từ 1
đến n từ trên xuống dưới. Các cột được đánh số từ 1 đến m từ trái qua phải. Giá
trị của số nguyên tại dòng i cột j là phép nhân i*j. Lấy tất cả các số trong ma trận
và sắp xếp tăng dần. Hãy tìm số nguyên thứ k trong dãy đã sắp xếp.
Trang 19
Dữ liệu nhập:

- Là ba số nguyên n, m, k cách nhau một khoảng trắng (1 ≤ n, m ≤ 105; 1 ≤ k ≤


n*m)

Dữ liệu xuất:

- Là số nguyên thứ k sau khi sắp xếp các số trong ma trận.


Ví dụ
INPUT OUTPUT
346 4

Trang 20
Trang 21

You might also like