Tải bản đầy đủ (.pdf) (24 trang)

Skkn tìm hiểu thời gian thực của thuật toán để lựa chọn thuật toán tối ưu trong các bài toán quen thuộc bằng ngôn ngữ lập trình c++

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 (314.4 KB, 24 trang )

Nội dung

Trang

1. Mở đầu

2

1.1. Lí do chon đề tài

2

1.2. Mục đích nghiên cứu

2

1.3. Đối tượng nghiên cứu

2

1.4. Phương pháp nghiên cứu

2

1.5. Những điểm mới của sáng kiến.

2

2. Nội dung của sáng kiến

2



2.1. Cơ sở lí luận của sáng kiến

2

2.2. Thực trạng vấn đề trước khi áp dụng sáng kiến

2

2.3. Nội dung chính của sáng kiến

3-20

2.4. Hiệu quả của sáng kiến kinh nghiệm đối với hoạt động giáo 20
dục với bản thân, đồng nghiệp và nhà trường
3. Kết luận và kiến nghị

21

4. Tài liệu tham khảo

22

5. Danh mục các đề tài SKKN mà tác giả đã được Sở 22
GD&ĐT xếp loại

1

skkn



1. MỞ ĐẦU
1.1Lý do chọn đề tài
Trong quá trình giảng dạy và đặc biệt trong các cuộc thi học sinh giỏi
chúng ta thường thấy có các bài yêu cầu các dữ liệu với nhiều bộ test khác
nhau. Khi gặp các bài toán này học sinh mới bắt đầu thường làm với thuật
toán đơn giản chỉ áp dụng cho dữ liệu nhỏ, vì vậy với những bộ test yêu cầu
số liệu lớn học sinh rất dễ mất điểm (test). Để giải quyết vấn đề này Tôi viết
sáng kiến kinh nghiệm với đề tài “ Tìm hiểu thời gian thực của thuật toán
để lựa chọn thuật toán tối ưu trong các bài tốn quen thuộc bằng ngơn
ngữ lập trình C++ ”. Nội dung của đề tài là kiến thức các thuật toán về số
nguyên tố và việc vận dụng các thuật toán đó trong giải quyết các bài tốn.
1.2 Mục đích nghiên cứu.
Làm tài liệu cho giáo viên phục vụ giảng dạy, bồi dưỡng học sinh giỏi.
1.3 Đối tượng nghiên cứu
Nhóm đội tuyển tin của nhà trường THPT Mai Anh Tuấn
1.4 Phương pháp nghiên cứu
- Phân loại theo thuật toán tương ứng với cái bài toán cụ thể.
- Tiến hành thực nghiệm.
- Đánh giá và tổng kết kinh nghiệm.
1.5 Những điểm mới của sáng kiến
Chia nhóm nghiên cứu thành hai nhóm , kết hợp giũa thuật toán và
thực hành để đánh giá mức độ vận dụng
2. NỘI DUNG SÁNG KIẾN KINH NGHIỆM
2.1. Cơ sở lí luận của sáng kiến kinh nghiệm.
Việc phân loại các dạng bài theo thuật toán sẽ làm cho học sinh dễ dang
nhận biết được thuật toán nào áp dụng cho bài toán nào một cách dễ dàng và
linh hoạt.
2.2. Thực trạng vấn đề trước khi áp dụng sáng kiến kinh nghiệm
Trong quá trình dạy đội tuyển những năm trước, học sinh nhiều khi không

biết cách sử dụng từng thuật toán cho từng bài bài, đẫn tới việc giải các bài
mất rất nhiều thời gian hoặc mất điểm do bị thiếu test do quá thời gian hoặc
bị giới hạn dữ liệu

2

skkn


2.3 Nội dung chính của sáng kiến
2.3.1 Tìm hiểu thời gian thực hiện thuật tốn
Có hai cách tiếp cận để đánh giá thời gian thực hiện của một thuật toán.
Cách thứ nhất bằng thực nghiệm, chúng ta viết chương trình và cho chạy
chương trình với các dữ liệu vào khác nhau trên một máy tính. Cách thứ hai
bằng phương pháp lí thuyết, chúng ta coi thời gian thực hiện thuật toán như
hàm số của cỡ dữ liệu vào (cỡ của dữ liệu vào là một tham số đặc trưng cho
dữ liệu vào, nó có ảnh hưởng quyết định đến thời gian thực hiện chương
trình. Ví dụ đối với bài tốn kiểm tra số nguyên tố thì cỡ của dữ liệu vào là
số cần kiểm tra; hay với bài toán sắp xếp dãy số, cỡ của dữ liệu vào là số
phần tử của dãy). Thông thường cỡ của dữ liệu vào là một số nguyên dương
n , ta sử dụng hàm số T(n) trong đó n là cỡ của dữ liệu vào để biểu diễn thời
gian thực hiện của một thuật toán.
Trong tài liệu này, chúng ta hiểu hàm số T(n) là thời gian nhiều nhất
cần thiết để thực hiện thuật toán với mọi bộ dữ liệu đầu vào cỡ n .
Sử dụng kí hiệu tốn học ơ lớn để mơ tả độ lớn của hàm. Giả sử n là
một số nguyên dương, T(n) và f(n) là hai hàm thực không âm. Ta viết T(n)=
O(f(n)) nếu và chỉ nếu tồn tại các hằng số dương c và n0 , sao cho T(n)≤ c x
f(n), mọi n ≥ n0.
Nếu một thuật toán có thời gian thực hiện T(n)= O(f(n)) chúng ta nói
rằng thuật tốn có thời gian thực hiện cấp f(n).

Ví dụ: Giả sử T(n) = n2 + 2n, ta có n2 + 2n ≤ 3n2 với mọi n ≥ 1.
Vậy T(n) = O(n2) trong trường hợp này ta nói thuật tốn có thời gian
thực hiện cấp n2.
2.3.2 Một số quy tắc đánh giá thời gian thực hiện thuật toán
Để đánh giá thời gian thực hiện thuật tốn được trình bày bằng ngôn
ngữ
C++, ta cần biết cách đánh giá thời gian thực hiện các câu lệnh của C++
Trước tiên, chúng ta hãy xem xét các câu lệnh chính trong C++. Các
câu lệnh trong C++ được định nghĩa như sau:
1. Các phép gán, đọc, viết là các câu lệnh (được gọi là lệnh đơn).
2. Nếu S1, S2, ..., Sm là câu lệnh thì { S1; S2; …; Sm; } là câu lệnh
(được gọi là lệnh hợp thành hay khối lệnh).
3. Nếu S1 và S2 là các câu lệnh và E là biểu thức lơgic thì If (E) S1;
else S2; là câu lệnh (được gọi là lệnh rẽ nhánh hay lệnh If).
3

skkn


4. Nếu S là câu lệnh và E là biểu thức lơgic thì
While (E) S; là câu lệnh (được gọi là lệnh lặp điều kiện trước hay lệnh
While).
5. Nếu S1, S2,…,Sm là các câu lệnh và E là biểu thức lơgic thì
Do
S1; S2; …; Sm;
While (E);
là câu lệnh (được gọi là lệnh lặp điều kiện sau hay lệnh Do .. While)
6. Nếu S là lệnh, E1 và E2 là các biểu thức cùng một kiểu thứ tự đếm
được Thì For (i=E1; i<= E2;i++) S; là câu lệnh (được gọi là lệnh lặp với số
lần xác định hay lệnh For).

Để đánh giá, chúng ta phân tích chương trình xuất phát từ các lệnh đơn,
rồi đánh giá các lệnh phức tạp hơn, cuối cùng đánh giá được thời gian thực
hiện của chương trình, cụ thể:
1. Thời gian thực hiện các lệnh đơn: gán, đọc, viết là O(1)
2. Lệnh hợp thành: giả sử thời gian thực hiện của S1, S2,…,S m tương
ứng là O((f1(n)), O((f2(n)), …, O((fm(n)). Khi đó thời gian thực hiện của lệnh
hợp thành là O(max(f1(n), f2(n), …, fm(n))).
3. Lệnh If: giả sử thời gian thực hiện của S1, S2 tương ứng là
Khi đó thời gian thực hiện của lệnh If là: O((f1(n)), O((f2(n)). Khi đó
thời gian thực hiện của lệnh If là O(max(f1(n), f2(n)))
4. Lệnh lặp While: giả sử thời gian thực hiện lệnh S (thân của lệnh
While) là O(f(n)) và g(n) là số lần lặp tối đa thực hiện lệnh S. Khi đó thời
gian thực hiện lệnh While là O(f(n)g(n)).
5. Lệnh lặp Repeat: giả sử thời gian thực hiện khối lệnh{ S1; S2;…;
Sm; }
là O(f(n)) và g(n) là số lần lặp tối đa. Khi đó thời gian thực hiện lệnh
Do..While là O(f(n)g(n)).
6. Lệnh lặp For: giả sử thời gian thực hiện lệnh S là O(f(n)) và g(n) là
số lần lặp tối đa. Khi đó thời gian thực hiện lệnh For là O(f(n)g(n)).
2.3.3 Ước lượng độ phức tạp thuật toán tương ứng với độ lớn dữ liệu
Độ phức tạp thuật toán là một hàm phụ thuộc đầu vào. Tuy nhiên
trong những ứng dụng thực tiễn, chúng ta khơng cần biết chính xác hàm này
mà chỉ cần biết một ước lượng đủ tốt của chúng. Việc ước lượng rất quan
4

skkn


trọng trong việc giải quyết bài tốn lập trình thi đấu, địi hỏi nhanh nhạy và
có quyết định chính xác ngay từ đầu. Độ phức tạp của thuật tốn có thể có

những giá trị là O(1), O(log n), O(n), O(nlogn), O(n 2), O(n3)… Và thời gian
thực hiện được so sánh như sau:
O(1)< O(log n) < O(n) < O(nlogn) < O(n2) < O(n3)…
Sau đây là bảng ước lượng (*) giới hạn dữ liệu vào tương ứng với độ
phức tạp của thuật toán đảm bảo thực hiện trong thời gian tối đa 1 giây.
Độ
tạp

phức O(1)

O(logn)

O(n)

Ước lượng Không ảnh hưởng thời 108
n tối đa
gian, thường chỉ phụ
thuộc giới hạn kiểu dữ
liệu

O(nlogn) O(n2) O(n3)
4.107

104

500

Với bảng ước lượng này giáo viên có thể hướng dẫn học sinh xác định độ
phức tạp của thuật toán phải đạt được để đảm bảo thời gian thực hiện. Từ đó
yêu cầu học sinh phải suy luận, tư duy để đạt được yêu cầu của bài toán.

2.3.4 Vận dụng một số dạng bài tốn
Dạng 1. Áp dụng định lí, cơng thức, kết quả… của tốn học có thể cho ta
cách giải rất tối ưu. Những cách giải đó có thể đáp ứng với dữ liệu lớn.
VD : Tính tổng S(N) = 12 + 22 +…+ N2
Dữ liệu vào: Từ tệp văn bản TONGBP.INP gồm:
 Dòng đầu chứa số nguyên dương TT là số lượng test (T ≤ 105)
 T dòng tiếp theo, mỗi dòng là một số nguyên dương N.
5

N ( N ≤ 10 ).

Kết quả: Ghi ra tệp văn bản TONGBP.OUT gồm T dòng, với mỗi dòng là giá
trị tổng S(N) tương ứng.
Ví dụ:
TONGBP.INP

TONGBP.OUT

2

14

3

1015
5

skkn



14
- 80% số test với 1 ≤ N, T ≤ 103

Giới hạn:

- 20% số test với 103 < N, T ≤ 105
Cách 1: -Tính lần lượt mỗi test, với mỗi test tính tổng bình phương các
số từ 1 đến n. Độ phức tạp sẽ là O(n2)
Cách 2: Theo tính chất dãy số trong tốn học thì:
12 + 22 +…+ N2 = N *(N+1) * (2*N +1) /6
Vận dụng tính chất này để tính mỗi Ti ta đã đưa độ phức tạp từ O(n2)
xuống O(n) với n là số lượng test
Với cách 1, độ phức tạp là O(n2). Đối chiếu theo bảng ước lượng (*) đã
nêu với thời gian thực hiện tối đa 1 giây thì đáp ứng được giới hạn với 1 ≤
N, T ≤ 103 chiếm 80% số test theo yêu cầu của đề.
Với cách 2, độ phức tạp O(n) đáp ứng được thời gian thực hiện tối đa 1
giây cho cả 20% số test còn lại với 103 < N, T ≤ 105
Code tham khảo
#include <bits/stdc++.h>
using namespace std;
int main()
{
freopen ("tongbp.inp" , "r" , stdin);
freopen ("tongbp.out" , "w" , stdout);
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
long long n,t; cin >> n;
for (long long i = 0 ; i < n ; i++)
{ cin >> t; cout << (t * (t + 1) * (2 * t + 1)) / 6 << '\n';


}

return 0;
}
Dạng 2. Kiểm tra tính nguyên tố của một số nguyên dương (N>0)
6

skkn


Cách 1: sử dụng hàm kiểm tra tính nguyên tố thông thường:
bool check(long long x)
{if (x==1) return false;
for(long long i=2;i<=sqrt(x);i++)
if (x%i==0) return false;
return true;
}
Cách 2: Sử dụng hàm kiểm tra nguyên tố dùng sàng nguyên tố  như sau:
bool isPrime[nmax];
void sieve()
{

memset(isPrime, true, sizeof(isPrime));
isPrime[1] = false;
for (long long i = 2; i*i <= nmax; i++)
if (isPrime[i])
for (long long j = i*i; j<= nmax; j+=i)
isPrime[j] = false;

}

So sánh độ phức tạp thuật toán là
So sánh

Kiểm tra nguyên tố

Sàng nguyên tố

Độ phức tạp

O(√ n ¿

O(nlogn)

Đặc điểm

Cài đặt đơn giản, dễ sử
dụng

Cài đặt phức tạp hơn

Trường hợp nên sử
dụng

- Kiểm tra tính nguyên
tố của ít số. Hoặc kiểm
tra số lượng phần tử
khoảng n≤ 105

- Kiểm tra và liệt kê
nhiều số nguyên tố


(thời gian thực hiện
≤1s)

-Kiểm tra số lượng phần
tử khoảng n≤ 107

Bài tập vận dụng:
Xét bài toán Số nguyên tố trong đoạn1
1

Bài N1002B trên />
7

skkn


Yêu cầu: Đếm số lượng số nguyên tố trong đoạn [a; b]. 
Dữ liệu: Một dòng ghi hai số nguyên a, b với 0 < a, b ≤ 107.
Kết quả: Một dòng là số lượng số ngun tố trong đoạn [a; b].
Ví dụ
input
1 10
output
4
Vì dữ liệu vào a, b ≤ 107, trường hợp lớn nhất là phải xét 107 số, với
mỗi số ai≤107 nên ta chọn cách 2 để viết
Code tham khảo
#include <bits/stdc++.h>
#define nmax 10000009

using namespace std;
bool isPrime[nmax];
void sieve()
{
memset(isPrime, true, sizeof(isPrime));
isPrime[1] = false;
for (long long i = 2; i*i <= nmax; i++)
if (isPrime[i])
for (long long j = i*i; j<= nmax; j+=i)
isPrime[j] = false;
}
int main()
{
sieve();
long long a, b;
cin >> a >> b ;

int dem = 0;

for (long long i = a; i <=b ; i++)
8

skkn


{if (isPrime[i]) dem++;}
cout << dem;
return 0;
}
Dạng 3: Thuật toán sắp xếp

* Thuật toán sắp xếp nổi bọt (Bubble Sort)
Đoạn chương trình thực hiện thuật tốn:
for (i = 1; i < n - 1; i++)
for (j = i + 1; j < n; j++)
if (a[i] > a[j])
swap(a[i],a[j];//Đổi chỗ a[i] với a[j]
Ưu điểm:
-

Code đơn giản, dễ hiểu.

-

Sắp xếp được với phần tử có miền giá trị lớn |ai| ≤ 1018

Nhược điểm:
-

Độ phức tạp của thuật tốn lớn O(n2).

Chạy khơng đủ nhanh (q 1 giây) với dữ liệu lớn, chỉ áp dụng với
số phần tử n ≤ 104.
-

* Thuật toán sắp xếp nhanh (Quick Sort)
Trong C++ đây là hàm có sẵn nên khi sử dụng chỉ cần gọi hàm
sort(a,a+n) để sắp xếp dãy từ a0,… ,an là được.
Nhận xét:
Ưu điểm:
Thời gian thực hiện nhanh. Độ phức tạp O(nlogn). Áp dụng với số phần

tử n ≤ 106.
-

-

Sắp xếp được các phần tử với miền giá trị lớn |ai| ≤ 1018

Nhược điểm:
-

Chạy chậm (quá 1 giây) với số phần tử n > 106.

Không ổn định, tùy thuộc vào cách chia thành 2 phần, nếu chia
không tốt độ phức tạp trong trường hợp xấu nhất có thể là O(n 2) (trường hợp
này hiếm khi xảy ra).
-

9

skkn


*Thuật toán sắp xếp bằng phương pháp đếm phân phối
Ý tưởng:
-

Khởi tạo giá trị ban đầu cho mảng dem.

-


Dùng mảng dem để đếm số lần xuất hiện của số a[i] trong dãy.

Duyệt i từ giá trị nhỏ nhất (gtmin) của các a[i] đến giá trị lớn nhất
(gtmax) của các a]i]. Duyệt j từ 1 đến dem[i] rồi in ra i.
-

Ưu điểm:
-

Code đơn giản. Độ phức tạp O(max(n,gtmax)) phụ thuộc miền giá

-

Áp dụng được với dãy có số phần tử lớn n ≤ 108.

trị.
Nhược điểm:
Miền giá trị > 107 sẽ không thể tạo được mảng dem để lưu trữ
(nghĩa là chỉ áp dụng được với miền giá trị |ai| ≤ 107).
-

Kết luận:
So sánh

Sắp xếp nổi bọt

Sắp xếp nhanh

Sắp xếp đếm phân phối


Độ phức tạp

O(n2)

O(nlogn) trường O(max(n,gtmax))
hợp xấu nhất có phụ thuộc miền giá trị
thể là O(n2)

Đặc điểm

Code đơn giản, Thời gian thực Code đơn giản
dễ hiểu
hiện
nhanh,
trong thư viện
C++ có sẵn hàm
sort

Trường hợp nên
sử dụng (thời
gian thực hiện
≤1s)

số phần tử n < số phần tử n ≤ số phần tử lớn n ≤ 108
104
106.
chỉ áp dụng được với
miền giá trị lớn | miền giá trị lớn | miền giá trị |ai| ≤ 107
ai| ≤ 1018
ai| ≤ 1018


Bài tập áp dụng
Bài 1: Cho một dãy số nguyên a1, a2, ..., an. Hãy đếm số lượng giá trị
khác nhau trong dãy và đưa ra số lần lặp của giá trị xuất hiện nhiều nhất.
Dữ liệu vào: File BAI1.INP gồm 2 dòng:
+ Dòng đầu số nguyên dương N (N <= 106)
+ Dòng thứ hai gồm dãy số nguyên a1, a2, ..., an.( |ai| <= 109)
10

skkn


Dữ liệu ra: File BAI1.OUT gồm 2 dòng:
+ Dòng đầu tiên ghi số lượng giá trị khác nhau trong dãy
+ Dòng thứ 2 ghi số lần lặp của giá trị xuất hiện nhiều nhất.
Ví dụ:
BAI1.INP

BAI1.OUT

8

5

67174668

3

- 60% số test với 1 ≤ N ≤ 104


Giới hạn:

- 40% số test với 104 < N ≤ 106
B1: Sắp xếp dãy số tăng dần. (các số bằng nhau đứng liên tiếp nhau)
B2: Khởi tạo d:=1 (số phần tử khác nhau), dd:=1 (số phần tử liên tiếp
bằng nhau) và dmax:=1 (số lần lặp của giá trị xuất hiện nhiều nhất). Duyệt
i từ phần tử thứ 2 đến n, nếu 2 số đứng cạnh nhau mà bằng nhau thì tăng dd,
ngược lại khác nhau thì tăng d, đồng thời tìm dmax.
Cách 1: nếu sử dụng thuật tốn sắp xếp nổi bọt thì chương trình lúc đó
sẽ có độ phức tạp là O(max(n2,n)) = O(n2)( O(n2) của đoạn chương trình sắp
xếp B1, O(n) của đoạn chương trình B2).
Cách 2: Nếu sử dụng thuật toán sắp xếp nhanh thì chương trình lúc đó
sẽ có độ phức tạp là O(max(=nlogn,n)) = O(nlogn) )( O(nlogn) của đoạn
chương trình sắp xếp B1, O(n) của đoạn chương trình B2).
Trường hợp nếu dữ liệu vào là |ai| <= 106, N <= 107 thì sử dụng thuật
toán đếm phân phối là phù hợp nhất.
Code tham khảo:
#include <bits/stdc++.h>
using namespace std;
long long n, d, dmax, dd, i, a[1000001] = {};
main(){
    freopen ("bai1.inp" , "r" , stdin);
    freopen ("bai1.out" , "w" , stdout);
    cin >> n;
    for (i = 0; i < n ; i++){
        cin >> a[i];
11

skkn



    }
    sort(a, a + n); //sắp xếp theo thứ tự từ bé đến lớn
    dd = 1; d = 1; dmax = 1;
    for (i = 1 ; i < n ; i++){
        if (a[i] == a[i - 1]){
            dd++;
        }
        else{
            if (dd > dmax){
                dmax = dd;
            }
            d++; dd= 1;
        }
    }
    cout << d << '\n' << dmax;
    return 0;}
Bài 2:

TRỒNG CÂY2

Bác Bình có một khu vườn rất nhiều cây, mỗi cây có một chiều cao
khác nhau. Bác Bình muốn chọn tất cả các cây đặc biệt trong vườn để trồng
thành một hàng cây mới (cây đặc biệt là cây có chiều cao là một số nguyên
tố). Hơn nữa để cây tiếp xúc tốt với ánh sáng bác có ý tưởng bố trí vị trí các
cây đặc biệt theo quy tắc thực hiện lần lượt như sau:
- B1: Chọn cây cao nhất đặt làm mốc;
- B2: Cây cao tiếp theo đặt ở bên trái của
mốc;
- B3: Cây cao tiếp theo nữa ở bên phải của

mốc;
Thực hiện lặp đi lặp lại B2, B3 cho đến khi
hết số cây đặc biệt.

Lần
4 Lần
2

Lần Lần Lần
5
1
3

Yêu cầu: Cho trước số lượng và chiều cao
của các cây trong vườn, hãy giúp bác Bình trồng hàng các cây đặc biệt theo
đúng ý tưởng của bác.
2

Nguồn Đề thi vào lớp 10 chuyên các tỉnh năm học 2020-2021

12

skkn


Dữ liệu vào: Từ tệp văn bản TRONGCAY.INP gồm hai dòng:
 Dòng thứ nhất chứa số nguyên N (1 ≤ N ≤ 106) là số lượng cây trong
vườn.
 Dòng thứ hai chứa N số nguyên ai (1 ≤ ai ≤ 107, 1 ≤ i ≤ N) là chiều cao
của cây thứ i.

Kết quả: Ghi ra tệp văn bản TRONGCAY.OUT gồm các số trên cùng một
dòng là chiều cao của các cây đặc biệt trong hàng cây mới. Nếu khơng có
cây đặc biệt nào thì ghi ra tệp giá trị ˗1.
Các số cách nhau ít nhất một dấu cách trống.
Ví dụ:
TRONGCAY.IN
P

TRONGCAY.OU
T

TRONGCAY.IN
P

TRONGCAY.OU
T

8

5 23 53 17 2

3

˗1

2 55 23 16 53
5 20 17
Giới hạn:

25 18


9

- 75% số test với 1 ≤ N ≤ 104.
- 25% số test với 104 < N ≤ 106.

Đặc điểm dữ liệu:
Dữ liệu vào N ≤ 106 và ai ≤ 107
Theo bảng ước lượng (*) :
- Cách 1: độ phức tạp O(n2) => chỉ đáp ứng được 75% số test với 1 ≤ N
≤ 104.
- Cách 2: độ phức tạp O(nlogn) => đáp ứng 100% số test của bài toán với
N ≤ 106
Vậy sử dụng thuật toán sắp xếp nhanh để cài đặt chương trình
Code tham khảo
#include

<bits/stdc++.h>

#define LL long long
#define TASK "TRONGCAY"
const int maxn=1e6+2;
const int q=1e7+2;
using namespace std;
13

skkn


int c[maxn], n; bool b[q];

///-------------------------void

sangnt()

{
memset(b,true,sizeof(b));
b[1]=false;
for(int i=2;i*i<=q;++i) if(b[i])
for(int j=i*i;j<=q;j+=i) b[j]=false;
}
///-------------------------void

solve()

{
sangnt();
cin>>n; int x,e,i=0;
for(int j=1;j<=n;++j)
{
cin>>x;
if(b[x]) { ++i; c[i]=x;}
}
if (i==0) cout<<-1;
sort(c+1,c+i+1);
if(i%2==0)
{
for(int j=1;j<=i;j+=2) cout<for(int j=i;j>1;j-=2) cout<}
else

{
for(int j=2;j<=i;j+=2) cout<for(int j=i;j>=1;j-=2) cout<14

skkn


}
}
///-------------------------int

main()

{
#ifndef ONLINE_JUDGE
freopen(TASK".inp", "r", stdin);
freopen(TASK".out", "w", stdout);
#endif // ONLINE_JUDGE
solve();}
Dạng 4: Sử dụng thuật tốn tìm kiếm
Ta xét bài tốn tìm kiếm dạng đơn giản sau:
Cho dãy A gồm N số nguyên A1, A2, …AN và số nguyên x. Cần biết có hay
không chỉ số i (1≤ i ≤ N) mà Ai = x. Nếu có hãy cho biết chỉ số đó.
Trong giới hạn đề tài, chúng ta sẽ tìm hiểu 2 thuật tốn tìm kiếm phổ
biến nhất là: tìm kiếm tuần tự và tìm kiếm nhị phân.
So sánh 2 thuật tốn:
So sánh

Tìm kiếm tuần tự


Tìm kiếm nhị phân

O(n¿

O(logn)

Độ phức tạp
Đặc điểm

Code đơn giản, dễ hiểu

Code phức tạp hơn

Trường hợp nên sử
dụng

- Dãy chưa sắp xếp

- Dãy đã sắp xếp

- tìm kiếm số lượng
phần tử khoảng n≤ 107
(giới hạn của mảng)

- Nếu dãy chưa sắp xếp
thì phụ thuộc thuật tốn
sắp xếp sử dụng.

(thời gian thực hiện 1s)

Bài tập áp dụng

Bài 1: Tìm kiếm nhị phân3
Cho dãy số a(n) nguyên dương. Tìm phần tử trong dãy a(n) có giá trị
bằng x
Dữ liệu: Dịng đầu ghi số nguyên dương n (n≤105)
3

Bài SB02B trên />
15

skkn


Dòng 2 ghi n số nguyên dương ai (ai ≤1018)
Dòng 3 ghi số nguyên dương T (T ≤105)
T dòng kế tiếp, mỗi dòng ghi số nguyên dương x
Kết quả: ghi ra T dòng, dòng thứ i ghi Y nếu trong dãy a(n) tồn tại
phần tử x, nếu không tồn tại ghi N
Giới hạn:

- 60% số test với 1 ≤ N ≤ 103.
- 40% số test với 103 < N ≤ 105.

Dữ liệu vào gồm dãy số a(n) nguyên dương (n≤105, ai ≤1018) và T test
(T≤105), mỗi test là một số ngun dương x
Đây là bài tốn tìm kiếm đơn giản, đa số học sinh sẽ dùng cách tìm
kiếm tuần tự. Tuy nhiên vì dữ liệu khá lớn (n≤105 ,T ≤105) nên ta xét các
cách giải sau:
Như vậy theo bảng ước lượng (*) :

Nếu sử dụng cách 1 (tìm kiếm tuần tự cho mỗi T) thì chỉ đáp ứng được
60% số test với 1 ≤ N ≤ 103, T≤105
Nếu sử dụng cách 2 (sắp xếp dãy an tăng dần theo sắp xếp nhanh và
dùng tìm kiếm nhị phân cho mỗi T) thì đáp ứng 100% test với N ≤ 105,
T≤105
Vậy lựa chọn cách 2 để cài đặt chương trình
Code tham khảo
#include<bits/stdc++.h>
using namespace std;
const long long maxn=1e5+7;
long long n,x,t;
long long a[maxn];
int nhiphan(long long B[], long long m, long long y)
{
int left = 0;
int right = m - 1;
int mid;
while (left <= right)
{
16

skkn


mid = (left + right) / 2;
if (B[mid] == y)
return mid; // tìm thấy x, trả về mid là vị trí của x trong mảng A
if (B[mid] > y)
right = mid - 1; // Giới hạn khoảng tìm kiếm lại là nửa khoảng
trước

else if (B[mid] < y)
left = mid + 1; // Giới hạn khoảng tìm kiếm lại là nửa khoảng
sau
}
return -1; // khơng tìm thấy x
}
int main()
{cin>>n;
for(int i=0;icin>>a[i];
sort(a,a+n);
cin>>t;
for(int j=1;j<=t;j++)
{cin>>x;
if (nhiphan(a,n,x)==-1)
cout<<"N"<<"\n";
else cout<<"Y"<<"\n";
}
return 0;
}

Bài 2: SEQ4
Cho dãy số gồm n số nguyên a1, a2, …, an và 2 số nguyên không âm L, R
(L ≤ R).
Yêu cầu: Đếm số cặp (i, j) thỏa mãn điều kiện: i ≤ j và L ≤ |ai+…+aj| ≤ R .
4

Nguồn Đề thi học sinh giỏi Tỉnh năm học 2016-2017

17


skkn


Dữ liệu vào: Từ file văn bản SEQ.INP gờm:
- Dịng đầu tiên chứa 3 số nguyên n, L, R (n ≤ 105 ; 0 ≤ L ≤ R ≤ 109)
- Dòng thứ hai chứa n số nguyên dương a1, a2,…, an (ai ≤ 109)
Kết quả: Ghi ra file văn bản SEQ.OUT gồm một số nguyên duy nhất là số
lượng cặp (i, j) đếm được.
Ví dụ:
SEQ.INP

SEQ.OUT

301

4

1 -1 2
Hạn chế:

- Có 50% số test ứng với 0 < n ≤ 103
- Có 50% số test ứng với 103 < n ≤ 105

Dữ liệu vào gồm dãy số a(n) nguyên dương (ai ≤ 109, n ≤ 105) và 2 số
nguyên L, R (0 ≤ L ≤ R ≤ 109)
Ta thấy dữ liệu vào L ≤ R ≤ 109 thì khơng thể sử dụng tìm kiếm tuần
tự được. Ta nghĩ đến cách tìm kiếm nhị phân, nhưng dãy này khơng thể sắp
xếp vì sẽ thay đổi vị trí các phần tử. Bài này vận dụng thuật tốn tìm kiếm
nhị phân linh hoạt như sau:

Trước hết tính tổng các số từ 1 đến n với s[i] = a[1]+a[2]+…+a[n]
Nhận xét tại vị trí j cần tìm vị trí i (i <= j) sao cho
l <= s[j]-s[i-1] <= r
=> s[j]-l <= s[i-1] <= s[j]-r
Ta dùng BIT để đếm số lượng các số trước đó thỏa điều kiện trên.
Nhận xét là nó bị kẹp nên ta dùng phần bù : chỉ việc lấy số lượng các số
>= s[j]-l trừ đi số lượng các số > s[j]-r thì ta sẽ được phần cần tìm
Vậy tạo một BIT để đếm, nhưng số lớn (10 9 và hơn thế nữa) nên ta cần
nén số lại, những số ta dùng hay nói cách khác là cập nhật và lấy giá trị là
s[i-1], s[i]-l và s[i]-r+1. Ta cho vào một mảng rồi sort lại, đẩy vào một mảng
mới tương ứng mỗi giá trị sẽ có một thứ tự 1 2 3… trên BIT. Cuối cùng
dùng chặt nhị phân để tìm kết quả
Như vậy theo bảng ước lượng (*) :
Nếu sử dụng cách 1 (Dùng mảng tính tổng rồi sử dụng tìm kiếm tuần tự
) thì chỉ đáp ứng được 60% số test với N ≤ 103
18

skkn


Nếu sử dụng cách 2 (theo ý tưởng như trên) thì đáp ứng 100% test với
N ≤ 105
Code tham khảo
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,L,R;
long long s[N];
int main()
{

freopen("SEQ.inp","r",stdin);
freopen("SEQ.out","w",stdout);
ios::sync_with_stdio(0);
cin>>n>>L>>R;
for (int i=1; i <= n; i++)
{
int a;
cin>>a;
s[i]=s[i-1]+a;
}
if (1LL*n*n <= 50000000)
{
long long res=0;
for (int i=1; i <= n; i++)
{
if (abs(s[i]) >= L && abs(s[i]) <= R)
res++;
for (int j=1; j < i; j++)
{
long long a=abs(s[i]-s[j]);
19

skkn


if (a >= L && a <= R)
res++;
}
}
cout<

return 0;
}
s[++n]=0;
sort(s+1,s+1+n);
int l=1, r=1;
long long res=0;
for (int i=2; i <= n; i++)
{
while (l <= i && abs(s[i]-s[l]) >= L)
l++;
if (l > 1 && abs(s[i]-s[l]) < L)
l--;
while (r <= i && abs(s[i]-s[r]) > R)
r++;
if (abs(s[i]-s[l]) >= L && abs(s[i]-s[r]) <= R)
res+=l-r+1;
}
cout<return 0;
}
2.4. Hiệu quả của sáng kiến kinh nghiệm đối với hoạt động giáo dục, với
bản thân, đồng nghiệp và nhà trường.
Khi tiến hành giảng dạy các nhóm lớp theo hướng đề tài nghiên cứu này.
Kết quả các em vận dụng rất tốt biết áp dụng thuật tốn vào từng bài một
cách nhanh chóng và đặc biệt hơn là không bị mất điểm
20

skkn




×