ĐỊNH HƢỚNG CHUYÊN MÔN o b ng h ọc A ĐỊNH...
Transcript of ĐỊNH HƢỚNG CHUYÊN MÔN o b ng h ọc A ĐỊNH...
1/64
ĐỊNH HƢỚNG CHUYÊN MÔN
trong đào tạo – bồi dƣỡng học sinh năng khiếu Tin học
A – ĐỊNH HƢỚNG
Các cuộc thi Olympic Tin học được triển khai rộng rãi ở gần hết các nước trên
thế giới hướng tới các mục đích:
Đẩy mạnh phong trào dạy và học Tin học nhằm đáp ứng các yêu cầu của
cuộc sống đang được tin học hóa sâu rộng và với tốc độ cao trong mọi
lĩnh vực,
Phát hiện các nhân tố nổi bật để đào tạo và khai thác nguồn nhân lực đỉnh
cao, có tri thức và có tay nghề theo kịp sự phát triển của lý thuyết và yêu
cầu của thực tế.
Việc đào tạo, bồi dưỡng học sinh giỏi Tin học chịu tác động rất nhiều của hai
yếu tố:
Sự phát triển của lý thuyết,
Sự phát triển của công cụ Tin học.
Có thể thấy rõ xu hướng dạy và học Tin học cho đến nay chia thành ba giai
đoạn:
Giai đoạn I: những năm cuối của thế kỷ XX,
Giai đoạn II: Thập kỷ đầu tiên của thế kỷ XXI,
Giai đoạn III: Thập kỷ thứ 2 của thế kỷ XXI, tức là những năm hiện tại.
Ở giai đoạn I, ngành Tin học mới tách ra và phát triển thành một ngành khoa
học độc lập. Người ta tập trung mọi sức lực vào việc xây dựng nền móng cho
một ngành khoa học mới, cố gắng tìm cách để giải quyết được các lớp bài toán
truyền thống xuất hiện khi tiếp cận bài toán trên quan điểm toán học. Việc
nắm vững các kiến thức toán học là điều kiện chủ yếu để giải quyết các bài toán
tin học. Ngoài ra còn phải lưu ý tới cấc ràng buộc tự nhiên về bộ nhớ nhỏ và tốc
độ tương đối thấp của máy tính lúc bấy giờ. Đó là sự tiếp tục của tư duy những
năm 70 – 80 của thế kỷ 20.
Ví dụ, bài Hình vuông diệu kỳ (Bài 3 ngày 2 IOI 1996) vào thời điểm đó là một
bài khó.
Hình vuông diệu kỳ
2/64
Những thành công của khối lập phương kỳ diệu đã là động lực để ông Rubic đề
xuất phương án phẳng của trò chơi nổi tiếng này. Đó là một bảng gồm 8 hình
vuông xếp thành 2 hàng (xem
hình vẽ).
Trong bài này chúng ta xét
phương án khi mỗi hình vuông
được tô một màu khác nhau. Màu
được đánh bằng tám số nguyên
dương đầu tiên (xem hình vẽ).
Cấu hình của bảng được xác định
bằng cách cho màu của các hình
vuông, bắt đầu từ hình ở góc trên
trái và đi theo chiều kim đồng hồ. Ví dụ, ở hình bên cấu hình ban đầu của bảng
được xác định bởi dãy (1, 2, 3, 4, 5, 6, 7, 8).
Có 3 phép biến đổi cơ sở ký hiệu là „A‟, „B‟ và „C‟ tác động lên bảng:
„A‟: Hoán đổi vị trí 2 dòng trên và dưới,
„B‟: Đẩy vòng tròn cột cuối lên cột đầu,
„C‟: Đẩy vòng tròn theo chiều kim đồng hồ 4 hình vuông ở giữa.
Cho một cấu hình cuối của bảng. Nhiệm vụ của bạn là viết chương trình xác
định dãy phép biến đổi cơ sở đưa bảng từ trạng thái ban đầu nêu ở hình bên về
trạng thái cuối đã cho (Bài toán con A). Bạn sẽ nhận thêm 2 điểm thưởng nếu số
phép biến đổi trong lời giải của bạn không quá 300.
Dữ liệu: vào từ file INPUT.TXT gồm một dòng chứa 8 số nguyên mô tả trạng
thái cuối của bảng.
Kết quả: Đưa ra file OUTPUT.OUT, dòng đầu tiên chứa số nguyên L – độ dài
của dãy phép biến đổi. Mỗi dòng trong L dòng sau chứa một ký tự - tên phép
biến đổi cần thực hiện.
Công cụ trợ giúp: Chương trình MTOOL.EXE trong thư mục chứa đề bài được
cung cấp để thử nghiệm thực hiện các phép biến đổi và xác định trạng thái cuối
của bảng. Lời gọi chương trình có dạng “MTOOL input.txt
output.txt”.
Ví dụ:
INPUT.TXT OUTPUT.TXT
1 2 3 4
8 7 6 5
1 2 34
8 7 65
1 7 2 4
8 6 3 5
1 2 3 4
8 7 6 5
Trạng thái ban đầu
Phép
biến
đổi
A
Phép biến đổi C
Phép biến đổi
B
Phép biến đổi
B
Phép biến
đổi A
Phép biến
đổi C
3/64
2 6 8 4 5 7 3 1 7
B
C
A
B
C
C
B
Phân tích: Số lượng trạng thái khác nhau của bảng là 8! = 40 320. Việc tổ chức
loang và lưu trữ vết loang từ trạng thái ban đầu tới trạng thái đích trong điều
kiện hiện nay không phải là một chuyện khó. Nhưng vào thời điểm diễn ra kỳ
thi, bộ nhớ có thể được sử dụng chỉ là 64KB + 64KB cấp phát động và máy
thực hiện thuộc loại 386 thì đó là cả một vấn đề lớn.
Chúng ta đã đạt được những kết quả rực rỡ trong thời kỳ này vì đội ngũ giáo
viên tin học phần lớn xuất thân từ giáo viên chuyên ngành toán. Học sinh của
chúng ta được trang bị kiến thức cơ sở về toán khá tốt.
Giai đoạn II từ những năm 2001 đến 2007, sự tiến bộ về công nghệ và sự phổ
cập của các hệ thống phần mềm tiên tiến đã đưa đến những sự chuyển dịch
trong việc đào tạo chuyên gia trong lĩnh vực tin học. Điều này dẫn đến những
thay đổi trong công tác phát hiện, đào tạo và bồi dưỡng học sinh giỏi tin học.
Do đó nội dung thi Tin học Quốc tế cũng có nhiều thay đổi. Các kiến thức giải
thuật được coi là đỉnh cao trước đây bây giờ đã trở thành “bảng cửu chương”
mà ai cũng phải biết và phải thuộc. Những giải thuật phức tạp, ít dùng thì không
nhất thiết phải thuộc, nhưng bất cứ ai và lúc nào cũng có thể tra cứu, tìm kiếm
chúng trên Internet khi cần thiết. Sự thông minh và tính sáng tạo bây giờ phải
thể hiện không phải ở chổ bạn thuộc nhiều hay ít các giải thuật khác nhau, cũng
không phải bạn cài đặt chúng nhanh tới mức nào. Thử thách bây giờ là ở chổ
bạn có thể tìm ra những giải pháp hữu hiệu giải quyết một cách có hiệu quả các
bài toán các vấn đề có mô hình toán học đơn giản nhưng có kích thước lớn hay
không?
Để đạt được mục đích đó người lập trình phải biết tận dụng tối đa khả năng mà
phần cứng và hệ điều hành cung cấp. Các hệ thống lập trình mới như Free
Pascal, Dev C++ (những phiên bản đầu tiên) đã tạo điều kiện để người dùng
khai thác được tối đa khả năng của phần cứng.
Đáng tiếc là tới tận năm 2007 khi thế giới đã đi hết chặng đường quan trọng này
thì chúng ta mới được phép chính thức đặt chân lên mãnh đất đã in đầy dấu
4/64
chân của những người tiên phong, đi vào con đường vốn bây giờ đã trở thành kỷ
niệm đẹp của những người đi trước.
Ở giai đoạn III, tức là ở những năm gần đây, trong danh mục yêu cầu đối với
người lập trình có thêm các đòi hỏi mới:
Không những bạn phải đứng vững trên nền tảng của phần cứng và hệ
thống mà còn phải biết khai thác tối đa khả năng của công cụ lập trình.
Cụ thể, bạn phải khai thác được triệt để mặt mạnh của các thư viện của
hệ thống lập trình có trong tay,
Nâng cao tính hoàn thiện của chương trình: chương trình phải cho kết quả
đúng và xử lý có hiệu quả với từng lớp dữ liệu (của bài toán).
Phải biết tạo một chương trình linh hoạt, kết hợp vài loại giải thuật để giải
một bài toán vốn rất đơn giản nếu kích thước bé.
5/64
Ví dụ, bài Vườn bách thảo (Thi toàn Liên bang Nga 04/2011).
VƢỜN BÁCH THẢO
Trong vườn bách thảo có một khu đất hình chữ nhật kích thước w×h trồng một
loại cây đặc biệt quý hiếm. Với mỗi cây người ta làm một lối đi tạo thành hình
vuông có cây ở tâm. Kích thước hình
vuông phụ thuộc vào vùng bộ rễ của cây
lan tới. Nếu tạo hệ tọa độ để khu đất này
có 2 đỉnh đối là (0, 0) và (w, h) thì đỉnh
tất cả các hình vuông đều có tọa độ
nguyên. Hai hình vuông bất kỳ không có
phần chung diện tích khác 0, tổng diện
tích các hình vuông có cây đúng bằng
diện tích khu đất, cạnh hình vuông song
song với trục tọa độ.
Thời gian trôi đi và cỏ mọc um tùm che
kín các lối đi. Người ta cần vẽ bản đồ
khôi phục lại các lối đi để lập trình điều
khiển rô bốt chăm sóc cây.
Yêu cầu: Cho w, h, n, xi, yi, trong đó n
là số cây, (xi, yi) – tọa độ cây thứ i (1 ≤ w, h ≤ 1012
, 1 ≤ n ≤ 2×105). Hãy xác
định độ dài cạnh hình vuông bao quanh mỗi cây. Dữ liệu đảm bảo tồn tại lời
giải.
Dữ liệu: Vào từ file văn bản GARDEN.INP:
Dòng đầu tiên chứa 3 số nguyên w, h và n,
Dòng thứ i trong n dòng sau chứa 2 số thực xi và yi.
Kết quả: Đưa ra file văn bản GARDEN.OUT n số nguyên trên n dòng, dòng
thứ i chứa cạnh hình vuông có tâm là cây thứ i.
Ví dụ:
GARDEN.INP GARDEN.OUT 4 6 3
1 1
3 1
2 4
2
2
4
Phân tích: Để đạt được điểm tối đa cần phải biết tổ chức lưu trữ dữ liệu dưới
dạng đồ thị cây, thực hiện các phép xử lý cây, tổ chức tìm kiếm nhị phân trên
trên đó.
x
y
1
1
(w, h)
6/64
Các hệ thống lập trình như Free Pascal 2.4.4, Dev C++ 4.9.9 đều cung cấp
những thư viện khổng lồ hỗ trợ người dùng và đều có thể mang lại những hiệu
quả tương đương trong lập trình. Tuy vậy C++ nhận được sự lựa chọn đông đảo
thí sinh dự thi hơn vì các lý do:
Thư viện chuẩn hộ trợ lập trình của C++ dễ tiếp cận hơn vì có nhiều tài
liệu giới thiệu,
Có nhiều phiên bản chương trình dịch miễn phí để những Ban tổ chức
cung cấp cho thí sinh,
Ở nhiều nước lấy C++ làm cơ sở giảng dạy ở trường phổ thông,
Trong tương lai, khi lên đại học người ta sẽ dùng C++ là chủ yếu,
Mặt mạnh của Pascal là khả năng thể hiện có cấu trúc trong quá trình
triển khai giải thuật không còn là trọng tâm trong nội dung giảng dạy,
C++ cho phép xây dựng chương trình tối ưu, thể hiện các tiểu xảo lập
trình.
Tuy vậy, C++ cũng có những mặt yếu của nó so với Pascal. Ta sẽ nói đến
những vấn đề này muộn hơn, khi đề cập tới công cụ lập trình.
B – CÔNG CỤ LẬP TRÌNH
Trang bị kiến thức về ngôn ngữ lập trình chưa bao giờ là một vấn đề lớn trong
tin học. Điều quan trọng là kỹ thuật lập trình và tổ chức dữ liệu. Khi đã biết
tương đối tốt một ngôn ngữ lập trình thì việc chuyển sang lập trình ở một ngôn
ngữ mới khá đơn giản.
Bên cạnh ngôn ngữ truyền thống PASCAL với hệ thống lập trình Free Pascal và
C++ với hệ thống lập trình DEV C++, nhiều nước còn cho phép và khuyến
khích sử dụng một loạt các ngôn ngữ khác như Delphi, Python, Java, C#, v . v .
. .
Đương nhiên, khi chọn một ngôn ngữ nào đó làm công cụ cho mình người lập
trình cần phải:
Biết rõ những điểm mạnh và yếu của ngôn ngữ cũng như của hệ thống hệ
thống lập trình hỗ trợ,
Cần nắm vững các dịch vụ mà hệ thống lập trình cung cấp,
Cần có thói quen suy nghĩ và hành động phù hợp với ngôn ngữ và hệ
thống lập trình,
Cần biết càng sâu càng tốt các thư viện chuẩn hỗ trợ lập trình và biết
khai thác chúng một cách tối ưu.
7/64
Nếu có cách tiếp cận hợp lý thì những vấn đề trên có thể giải quyết được một
cách khá đơn giản và hiệu quả.
Ở nước ta, trong bậc PTTH hệ thống lập trình được sử dụng phổ biến là ngôn
ngữ PASCAL với hệ thống lập trình Free Pascal. Ở bậc đại học, ngôn ngữ lập
trình chủ yếu là C/C++ với hệ thống Dev C++.
Trên thế giới nhiều nước sử dụng C++ với các hệ thống lập trình Dev C++ hoặc
tương đươngngay từ bậc phổ thông trung học.
Việc giảng dạy đại trà trong nhà trường không phải là vấn đề thảo luận ở đây.
Nhưng việc trang bị công cụ cho học sinh năng khiếu, phục vụ cho các kỳ thi
Tin học là vấn đề nằm trong tầm xem xét và xử lý của chúng ta.
Các thành phần và cấu trúc tƣơng đƣơng của 2 hệ thống lập trình
Các phép tính số học
Stt PASCAL C/C++ Ví dụ
Pascal C/C++
+ + a + b a + b
- - a - b a - b
* * a * b a * b
/ / a / b a / b
DIV / n div m n / m
MOD % n mod m n % m
Các phép tính quan hệ
Stt PASCAL C/C++ Ví dụ
Pascal C/C++
< < a < b a < b
<= <= a <= b a <= b
= == a = b a == b
>= >= a >= b a >= b
<> != a<>b a!=b
and && Pascal sử dụng các
phép xử lý bit để tạo
biểu thức quan hệ.
or ||
not !
8/64
Các phép tính xử lý bit
Stt PASCAL C/C++ Ví dụ
Pascal C/C++
not ~
and |
or &
xor ^
shl <<
shr >>
Khai báo
Lệnh gán
PASCAL
Var i,j,k:integer;
m,n: longint;
a,b:real;
p,d:int64;
x:array[0..20] of longint;
y:array[1..10, 1..5] of longint;
z:array[1..15] of real;
c:char;
s:string;
C++
int i,j,k;
long m,n,x[21], y[10][5];
float a,b,z[15];
long long p,d;
char c;
string s;
PASCAL
a:=y[i,j];
a:=a+b;
a:=a*z[i];
i:=i+1;
k:=i div j;
k:=i mod j;
C++
a=y[i][j];
a+=b; // a=a+b;
a*=z[i];
i++; // ++i;
k=i/j;
k=i%j;
9/64
Lệnh IF
Câu lệnh FOR
Lƣu ý: Có thể khai báo biến i cục bộ hóa trong chu trình:
PASCAL
if a<> b then c=c+5;
if a= b then z[i]:=0 else
begin
z[i]:= z[i]+c;
z[n-i]:=z[n-i]-c
end;
C++
if (a!=b) c+=5;
if (a==b)z[i]=0;
else
{z[i]+=c;
Z[n-i]-=c;
}
Đặt điều kiện
trong ngoặc Lưu ý có ;
Lưu ý có ;
Không có then
PASCAL
for i:=1 to n do
begin
. . . .
end;
for j:= n downto 1 do
begin
. . . .
end;
C++
for (i=1;i<=n;i++)
{
. . . .
}
for (i=n;i>0;i--)
{
. . . .
}
C++
for (int i=1;i<=n;i++){ . . . }
for (int i=n;i>0;i--) { . . . }
10/64
Tổ chức chu trình có số lần lặp không biết trƣớc
Phần đầu chƣơng trình
Ở PASCAL có thể có hoặc không có phần đầu chương trình:
Ở C++: bắt buộc phải khai báo các thư viện:
PASCAL
repeat
. . . . .
until j=i;
while i<j do
begin
. . . .
end;
C++
do
{
. . . .
} while (i==j);
while (i<j)
{
. . . .
}
PASCAL
Program NIM;
Uses crt, matrix;
Khai báo thư viện
C++
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
Khai báo thư viện,
thừa không sao!
11/64
Tổ chức vào ra với file văn bản
Chƣơng trình con và chƣơng trình chính
PASCAL
Const tfi=‟NIM.INP‟;
Tfo=‟NIM.OUT‟;
Var fi,fo:text;
. . . . . . .
Assign(fi,tfi); reset(fi);
Readln(fi,a,b,c);
Close(fi);
. . . . . . .
Assign(fo,tfo);
Rewrite(fo);
Writeln(fo,a,‟ „,b);
Write(fo,c);
Close(fo);
C++
ifstream fi (“NIM.INP”);
ofstream fo (“NIM.OUT”);
. . . . . . . .
fi>>a>>b>>c;
fi.close();
. . . . . . . .
fo<<a<<” “<<b<<endl;
fo<<c;
fo.close();
Trong phần khai báo.
Flies tự động được mở,
PASCAL
Procedure p1(i,j:integer);
Begin . . . End;
Procedure p2;
Begin . . . End;
Function fact(i:integer);integer;
Begin
. . . . . .
fact:=k
End;
. . . . . .
BEGIN
. . . .
END.
C++
void p1(int i,int j)
{ . . . . }
void p2()
{ . . . . }
int fact(int i)
{ . . .
return(k);
}
. . . . . . .
int main()
{ . . . .
}
12/64
Ví dụ:
Xử lý xâu
PASCAL Program NIM;
Const tfi='NIM.INP';
tfo='NIM.OUT';
Var a:array[1..1000] of longint;
i,n,g,t,k:longint;
fi,fo:text;
BEGIN
assign(fi,tfi);
reset(fi);
readln(fi,n);
for i := 1 to n do readln(fi,a[i]);
close(fi);
g:=0;
for i:=1 to n do g:=g xor a[i];
k:=0;
for i:=1 to n do
begin
t:=a[i]-(a[i] xor g);
if t>0 then
begin
inc(k);
a[i]:=t
end
else a[i]:=0
end;
assign(fo,tfo); rewrite(fo);
writeln(fo,k);
if k > 0 then
for i:=1 to n do if a[i]>0 then
writeln(fo,i,' ',a[i]);
close(fo)
END.
C++ #include <fstream>
#include <conio.h>
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{long n,g,t,k;
ifstream fi ("NIM.INP");
ofstream fo ("NIM.OUT");
fi >> n;
{long a[n+1];
for (long i = 1; i <=n;i++)
fi>>a[i];
k=0;
g=0;
for (long i=1; i <=n;i++) g^=a[i];
if (g>0)
{ for (long i=1;i<=n;i++)
{ t = a[i] – (a[i]^g);
if (t>0)
{k++;a[i]=t;
} else a[i]=0;
}
}
fo<<k<<endl;
if (k>0)
for (long i= 1; i<=n;i++) if
(a[i]>0) fo<<i << " "<<a[i]<<endl;
fi.close(); fo.close();
}
}
13/64
Xâu là một trong các loại dữ liệu cơ bản mà các hệ thống lập trình đều phải
cung cấp địch vụ để khai báo, lưu trữ và xử lý.
Cả trong PASCAL và C++ đều có 2 loại xâu:
Trong PASCAL:
o Xâu kiểu string (ngắn và dài),
o Xâu kiểu Pchar trong hệ thống lập trình Free Pascal,
Trong C++:
o Xâu dạng C,
o Xâu dạng C++.
Tồn tại một loạt các dịch vụ cung cấp các phép xử lý xâu loại 2 và chuyển đổi
dạng biểu diễn xâu. Các dịch vụ này được tổ chức trong thư viện Strings của
PASCAL và string của C++.
string s(“ABCD12345abcdABCDE2”);
string ns(“0123456789”);
l=s.size(); // l=s.length(); 19 l
k=s.find(“BC”); // 1 k (bắt đầu từ 0)
m=s.rfind(“BC”); // 14 m
p=s.find_first_of(„C‟); // 2 p
q=s.find_last_of(„C‟); // 15 q
i=s.find_first_of(ns); // 4 i
j=s.find_last_not_of(ns); // 17 j
14/64
C++
#include <fstream>
#include <string>
#include <iostream>
using namespace std;
int main()
{ string
s=("ABCD12345abcdABCDE2");
string ns=("0123456789");
int l,k,m,p,q,i,j;
ofstream fo ("xl_xau.out");
l=s.size();
k=s.find("BC");
m=s.rfind("BC");
p=s.find_first_of('C');
q=s.find_last_of('C');
i=s.find_first_of(ns);
j=s.find_last_not_of(ns);
fo<<"L = "<<l<<endl;
fo<<"K = "<<k<<endl;
fo<<"M = "<<m<<endl;
fo<<"P = "<<p<<endl;
fo<<"Q = "<<q<<endl;
fo<<"I = "<<i<<endl;
fo<<"J = "<<j<<endl;
fo.close();
}
XL_XAU.OUT
L = 19
K = 1
M = 14
P = 2
Q = 15
I = 4
J = 17
15/64
PASCAL cũng cung cấp những dịch vụ tương tự trong thư viện Strings. Dưới
đây là vài ví dụ nêu trong tài liệu hướng dẫn sử dụng của Free Pascal.
PASCAL
Program Example13 ;
Uses s t r i n g s ;
{ Program to demonstrate the StrScan and StrRScan f u n c t i o n s . }
Const P : PChar = ’ This i s a PCHAR s t r i n g . ’ ;
S : Char = ’ s ’ ;
begin
Writeln ( ’P , s t a r t i n g from f i r s t ’ ’ s ’ ’ : ’ ,StrScan (P, s ) ) ;
Writeln ( ’P , s t a r t i n g from l a s t ’ ’ s ’ ’ : ’ ,StrRScan (P, s ) ) ;
end .
Find_first và Find_last
PASCAL
Program Example14 ;
Uses s t r i n g s ;
{ Program to demonstrate the StrLower and StrUpper f u n c t i o n s . }
Const P1 : PChar = ’THIS IS AN UPPERCASE PCHAR STRING ’ ;
P2 : PChar = ’ t h i s i s a lowercase s t r i n g ’ ;
begin
Writeln ( ’ Uppercase : ’ ,StrUpper (P2 ) ) ;
StrLower ( P1 ) ;
Writeln ( ’ Lowercase : ’ ,P1 ) ;
end .
Chữ hoa và chữ thường
(có công cụ tương đương trong C)
16/64
Như vậy, việc dùng PASCAL hay C++ đều có thể giải quyết một cách hiệu quả
các bài toán thi học sinh giỏi. Tuy vậy, trong mọi trường hợp, học sinh cần
được:
Trang bị thêm các kiến thức mới,
Có thói quen khai thác các công cụ trong hệ thống lập trình,
Biết cách sử dụng hợp lý và có hiệu quả các công cụ hiện có.
Đã là một công dạy và học, tại sao ta không làm việc ngay với C++? Điều này
là hoàn toàn khả thi và có lợi vì:
Về mặt pháp lý:
o Bộ Giáo dục cho phép sử dụng C/C++ trong kỳ thi,
o Công cụ: cũng thuộc loại Open Sources (miễn phí),
Về thời gian:
o 02 tiết cho việc giới thiệu cách viết chương trình trên C++,
o 02 tiết cho việc làm quen với môi trường lập trình Dev C++,
o Các kiến thức khác, nếu cần, trang bị dần trong suốt quá trình bồi
dưỡng (theo nguyên tắc “Mưa dầm thấm đất”),
Lợi ích:
o Thư viện chuẩn STL của C++ có nhiều dịch vụ cần thiết với học
sinh (ví dụ như các công cụ sắp xếp theo các giải thuật khác nhau,
các công cụ tìm kiếm, tổ chức stack, heap, xử lý vector, ma trận v.
v. . .),
o Tài liệu: rất phong phú,
PASCAL
Program Example15 ;
Uses s t r i n g s ;
{ Program to demonstrate the StrPos f u n c t i o n . }
Const P : PChar = ’ This i s a PChar s t r i n g . ’ ;
S : Pchar = ’ i s ’ ;
begin
Writeln ( ’ Pos i t ion of ’ ’ i s ’ ’ i n P : ’ , s i z e i n t ( StrPos (P,S)) - s i z e i n t (P ) ) ;
end .
Tìm vị trí xâu con
17/64
Về mục tiêu chiến lược lâu dài:
o Các kiến thức về kỹ năng lập trình sẽ được tận dụng triệt để trong
tương lai khi học sinh lên đại học và ra làm việc,
o Đáp ứng yêu cầu về chiến lược đào tạo và nâng cao chất lượng
Giáo dục của nhà nước.
Tất nhiên PASCAL với hệ thống lập trình Free Pascal vẫn được sử dụng song
song như một tùy chọn cho những người yêu thích và vẫn hoàn toàn có thể đạt
kết quả cao trong các kỳ thi.
Các vấn đề cần lƣu ý khi chuyển đổi công cụ
Trong thời gian đầu cần trợ giúp học sinh các vấn đề:
Các loại lỗi cú pháp thường gặp ở giai đoạn đầu,
Cách tạo file dữ liệu,
Kỹ thuật cục bộ hóa biến,
Khai thác giao diện của Dev C++ và các công cụ hiệu chỉnh,
Vấn đề biến đổi kiểu dữ liệu trong biểu thức,
Giới thiệu về sự tồn tại và tác dụng của một số thư viện trong STL.
Tránh trang bị dồn dập kiến thức mới, đặc biệt là về các thư viện. Phong cách
lập trình theo kiểu C++ sẽ tự động được hình thành dần theo thời gian, đúng với
tinh thần “Trăng đến rằm sẽ tròn”.
Các vấn đề này sẽ được xem xét ở các phần tiếp theo.
18/64
SẮP XẾP
Hàm sắp xếp nhanh trên C++
void quickSort(int arr[], int left, int right) { int i = left, j = right; int tmp; int pivot = arr[(left + right) / 2]; /* partition */ while (i <= j) { while (arr[i] < pivot) i++; while (arr[j] > pivot) j--; if (i <= j) { tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; i++; j--; } }; /* recursion */ if (left < j) quickSort(arr, left, j); if (i < right) quickSort(arr, i, right); }
Trong nhiều trường hợp thường phải sắp xếp đồng thời 2 dãy Chỉ số CS và Giá
trị B, tức là sắp xếp cặp (csi, bi) theo giá trị tăng dần của bi và với những bi
bằng nhau cặp (csi, bi) có csi nhỏ hơn sẽ đứng trước.
SẮP XẾP 2 DÃY
Ta có thể dễ dàng bổ sung mở rộng hàm sắp xếp nêu trên để giải quyết vấn đề
này. Tuy vậy, để tận dụng tối đa các khả năng sắp xếp khác nhau do hệ thống
lập trình cung cấp ta cần tổ chức dữ liệu theo kiểu véc tơ, heap. . . . Chương
trình sắp xếp sẽ có dạng:
Chƣơng trình trên C chuẩn:
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
typedef struct
121 265 147 3 27 Chưa sắp xếp
121 265 147 3 27 Giá trị trục = 7
Giá trị trục
121 265 147 3 27 12 ≥ 7 ≥ 2 đổi chổ
21 265 147 3 127 26 ≥ 7 ≥ 7 đổi chổ
21 75 147 3 1226 7 ≥ 7 ≥ 3 đổi chổ
21 75 143 7 1226 i > j - kết thúc xử lý nhóm
i
i
i
i
i
j
j
j
j
j
21 75 143 7 1226
Xử lý đệ quy các nhóm
21 53 77 12 2614
19/64
{
int val, idx; //val: b[i], idx: cs[i]
}Element;
typedef Element Arr[MAX];
int cmpf(const void *a, const void *b);
int main()
{
Arr a = {{5, 4}, {3, 2}, {5, 3}, {5, 1}, {2, 5}};
int n = 5;
qsort(a, n, sizeof(Element), cmpf);
for (int i=0; i<n; i++)
printf("%d %d\n", a[i].val, a[i].idx);
getchar();
}
int cmpf(const void *a, const void *b)
{
return ((((Element *)a)->val - ((Element *)b)->val>0)||
(((Element *)a)->val == ((Element *)b)->val)&&(((Element
*)a)->idx > ((Element *)b)->idx));
}
Chƣơng trình trên C++:
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
typedef class CElement
{
int val, idx; //val: b[i], idx: cs[i]
public:
CElement(int v=1, int i=1)
{
val = v; idx = i;
}
int GetValue()
{
return val;
}
};
int cmpf(const void *a, const void *b);
20/64
typedef class CArr
{
int n;
CElement data[MAX];
public:
CArr()
{
}
void Sort()
{
qsort(data, n, sizeof(CElement), cmpf);
}
};
int main()
{
CArr a;
//Nhap du lieu cho a
//....
a.Sort();
//...
//Xuat ket qua cua a
system("PAUSE");
return 0;
}
int cmpf(const void *a, const void *b)
{
return (((CElement *)a)->GetValue() - ((CElement *)b)-
>GetValue());
}
Ghi chú: Nội dung của phần sắp xếp và sắp xếp 2 dãy được biên soạn theo tài liệu của thầy
Nguyễn Thanh Sơn, trường Phổ thông Năng khiếu , Đại học Quốc gia Thành phố Hồ Chí
Minh.
21/64
BÀI TẬP VÀ CHƢƠNG TRÌNH
Để hỗ trợ cho việc triển khai giảng dạy trên C++ các bài tập dưới đây sẽ được giới thiệu kèm
theo với lời giải trên ngôn ngữ C/C++ và Pascal (dựa trên cơ sở hệ thống lập trình Free
Pascal). Khi đã có đủ tự tin trong việc lập trình ta sẽ thấy lời giải đưa ra dưới dạng chương
trình hoàn thiện chỉ có tác dụng chủ yếu để tạo tests.
Việc trình bày giải thuật dưới dạng sơ đồ khối cũng không giúp nhiều cho việc nắm bắt giải
thuật mà chỉ hỗ trợ cho việc viết chương trình (coding) được dễ dàng. Ngoài ra, sơ đồ khối
còn hạn chế tính sáng tạo trong việc triển khai giải thuật.
Với mỗi bài toán, quan trọng và cần thiết hơn cả là gợi ý về giải thuật, bao gồm:
Hướng triển khai giải thuật,
Cần có những dữ liệu gì, tổ chức như thế nào, giá trị đầu, cách biến đổi, dẫn xuất,
Những điểm đặc biệt cần lưu ý,
Đánh giá độ phức tạp,
Với một vài khâu xử lý quan trọng: có thể dẫn xuất câu lệnh hoặc đoạn chương trình.
Việc dẫn xuất đầy đủ chương trình cũng rất cần thiết, nhưng chỉ ở giai đoạn đầu của quá trình
đào tạo, bồi dưỡng học sinh.
Việc tổ chức gợi ý giải thuật còn mất nhiều thời gian và công sức hơn cả việc trực tiếp viết
chương trình giải!
Tuyệt đối tránh cung cấp cho học sinh chương trình hoàn thiện ngay từ đầu, khi học sinh
chưa hiểu giải thuật, chưa bắt tay tự mình lập trình.
Lời giải cho các bài tập ở phần đầu được đưa ra ở dạng song ngữ. Ở nửa cuối của phần bài
tập, khi đã có đủ kinh nghiệm với C++, các lời giải chỉ đưa ra ở dạng chương trình trên C++
hoặc C/C++.
22/64
TẬP CON Tên chương trình: SUBSETS.???
Cho 2 tập số nguyên X={x1, x2, . . ., xm} và Y={y1, y2, . . ., yn}, trong đó:
x1 < x2 < . . . < xm,
y1 < y2 < . . . < yn.
Nói X có thứ tự từ điển nhỏ hơn Y nếu thỏa mãn một trong hai điều kiện:
i sao cho x1 = y1, x2 = y2 , . . ., xi-1 = yi-1, xi < yi,
m < n và x1 = y1, x2 = y2 , . . ., xm = ym.
Xét tập số nguyên {1, 2, . . ., n}. Từ tập này người ta có thể trích ra các tập con, trong mỗi tập
con các phần tử được liệt kê theo thứ tự tăng dần của giá trị và sắp xếp các tập con nhận được
theo thứ tự tự điển tăng dần. Các tập con được đánh số bắt đầu từ 1.
Ví dụ, với n = 3 ta có các tập con:
1: { 1 }
2: { 1, 2}
3: { 1, 2, 3}
4: { 1, 3 }
5: { 2 }
6: { 2, 3}
7: { 3 }
Yêu cầu: Cho hai số nguyên n và k (1 < n ≤ 60, 1 ≤ k ≤ 260
-1). Hãy xác định tập con thứ k.
Dữ liệu: Vào từ file văn bản SUBSETS.INP gồm nhiều tets, mỗi test cho trên một dòng chứa
2 số nguyên n và k.
Kết quả: Đưa ra file văn bản SUBSETS.OUT, kết quả mỗi test đưa ra trên một dòng chứa các
số nguyên xác định tập tìm được.
Ví dụ:
SUBSETS.INP SUBSETS.OUT 3 2
4 9 1 2
2
23/64
1
1 2
1 2 3
1 3
2
2 3
3
1 0
2 1
3 2
4 3
5 4
6 5
7 6
k k-1
k--
i =n-1 ¸ 0
Bit thứ i của
k t
t = 0
di=1di=0
k--
k = 0
i =n-1 ¸ 0
di = 1
Đưa ra i
Exit
T
T
T
F F
F
C++ #include <fstream>
#include <string>
int n,d[61];
long long k,t,t1=1;;
using namespace std;
ifstream fi ("SUBSETS.INP");
ofstream fo ("SUBSETS.OUT");
void xly()
{ k--;
for (int i=n-1; i>=0;i--)
{ t=(t1<<i)&k;
if ( t==0)
{d[n-i]=1; if (k==0) break;k--;}
else d[n-i]=0;
}
for (int i=1;i<=n;i++) if (d[i]==1) fo<<i<<" ";
fo<<endl;
}
int main()
{ while (! fi.eof())
{ fi>>n>>k;
//if (n==0) break;
xly();
}
fi.close();
fo.close();
}
24/64
PASCAL Program SUBSETS;
Const tfi='SUBSETS.INP';
tfo='SUBSETS.OUT';
t1:int64=1;
Var n:integer;
k,t:int64;
d:array[1..60] of byte;
fi,fo:text;
Procedure xly;
var i:integer;
Begin
dec(k);
for i:= n-1 downto 0 do
begin
t:=(t1 shl i) and k;
if t=0 then
begin d[n-i]:=1;
if k = 0 then break; dec(k)
end
else begin d[n-i]:=0; end
end;
for i:=1 to n do if d[i]=1 then write(fo,i,' ');
writeln(fo)
End;
BEGIN
assign(fi,tfi); reset(fi);
assign(fo,tfo); rewrite(fo);
while not eof(fi) do
begin
readln(fi, n, k);
xly
end;
close(fi); close(fo)
END.
25/64
INTERNET Tên chương trình: INTERNET.???
Giá truy cập internet phụ thuộc vào từng thời điểm trong tuần. Các ngày trong tuần được
đánh số từ 1 đến 7 bắt đầu từ thứ 2. Bảng tính giá gồm n bản ghi (1 ≤ n ≤ 100), sắp xếp theo
thứ tự tăng dần của thời gian, mỗi bản ghi có dạng:
d cc:mm v
trong đó d là ngày trong tuần, cc – 2 số chỉ giờ (chế độ 24 giờ), mm – 2 số chỉ phút, v – giá
mỗi phút truy nhập, là số nguyên (1 ≤ v ≤ 104). Giá truy nhập được giữ nguyên cho đến khi
gặp thời điểm mới ở bản ghi tiếp theo trong bảng. Sau bản ghi thứ n là bảng ghi số 1.
Yêu cầu: Cho bảng tính giá và danh sách xác định m phiên truy nhập mạng (1 ≤ m ≤ 105).
Mỗi phần tử của danh sách có dạng d cc:mm t, trong đó d, cc, mm có ý nghĩa như trong
bảng giá, t – thời gian truy nhập tính theo phút, là số nguyên (1 ≤ t ≤ 109). Các phiên truy
nhập có thể thuộc những tuần khác nhau. Hãy tính chi phí “lướt” mạng.
Dữ liệu: Vào từ file văn bản INTERNET.INP:
Dòng đầu tiên chứa 2 số nguyên n và m,
Mỗi dòng trong n dòng tiếp theo chứa một bảng ghi của bảng giá,
Ở m dòng cuối cùng mỗi dòng chứa một bản ghi về một phiên truy nhập.
Kết quả: Đưa ra file văn bản INTERNET.OUT một số nguyên – chi phí phải trả.
Ví dụ:
INTERNET.INP INTERNET.OUT 2 3
1 09:00 500
5 22:00 200
2 22:42 16
5 21:06 57
2 22:50 1
36100
26/64
C++ #include <fstream>
#include <string>
using namespace std;
long n,m,d,v,t,a[10081];
long long r;
string s;
ifstream fi ("INTERNET.INP");
ofstream fo ("INTERNET.OUT");
void nhap()
{ long i,j;
s.reserve(6);
fi>>d>>s>>v;
i=(s[0]-48)*10+s[1]-48;
j=(s[3]-48)*10+s[4]-48;
t= --d*1440+i*60+j+1;
}
void xd_a()
{
memset(a,0,sizeof(a));
for (int i=1;i<=n; i++)
{ nhap();
a[t]=v;
}
for (int i=1;i<=10080;++i)
{if (a[i]!=0) v=a[i];
a[i]=a[i-1]+v;
}
}
void xd_cf()
{long p,q;
p=v/10080; q=v%10080;
r+=a[10080]*p;
if (t+q>10081){r+= (a[10080]-a[t-1]);q-= (10081-t);t=1;}
r+=(a[t+q-1]-a[t-1]);
}
int main()
{fi>>n>>m;
r=0;
xd_a();
for (int i=1; i <=m; i++)
{nhap();
xd_cf();
}
fo<<r;
fi.close(); fo.close();
}
27/64
PASCAL Program Internet;
Const tfi = 'INTERNET.INP';
tfo = 'INTERNET.OUT';
Var n,m,d,v,i:integer;
t:longint;
r:int64;
a:array[-1..10079] of longint;
fi,fo:text;
Procedure nhapdl;
var i,j:longint;
s:string[6];
Begin
readln(fi,d,s,v);
dec(d);
i:=(ord(s[2])-48)*10+ord(s[3])-48;
j:=(ord(s[5])-48)*10+ord(s[6])-48;
t:=d*1440+i*60+j
End;
Procedure xd_a;
var i:longint;
Begin
fillchar(a,sizeof(a),0);
for i:=1 to n do
begin
nhapdl;
a[t]:=v;
end;
for i:=0 to 10079 do
begin
if a[i]<>0 then v:=a[i];
a[i]:=a[i-1]+v
end
End;
Procedure xd_cf;
var p,q:longint;
Begin
p:=v div 10080; q:= v mod 10080;
r:=r+a[10079]*p;
if q+t>10080 then
begin r:=r+a[10079]-a[t-1];q:=q-10080+t;t:=0 end;
r:=r+a[q+t-1]-a[t-1]
End;
BEGIN
assign(fi,tfi); reset(fi);
readln(fi,n,m);
xd_a; r:=0;
for i:= 1 to m do
begin
nhapdl;
xd_cf
end;
assign(fo,tfo); rewrite(fo);
write(fo,r);
close(fi); close(fo)
END.
28/64
BÁNH XE MAY MẮN Tên chương trình: WHEEL.???
Hồi còn bé Steve được tặng một đồ chơi gọi là bánh xe may mắn. Đó là một cái đĩa có thể quay theo
chiều kim đồng hồ. Vành của đĩa được chia thành n phần bằng nhau, trên mỗi phần có thể ghi một
chữ cái latinh in hoa. Bên ngoài có một kim chỉ đến chữ cái
khi bánh xe dừng. Thiết kế đồ chơi đảm bảo để kim không
bao giờ chỉ đúng vào vạch phân chia. Cứ mỗi lần kim chạm
vào vạch phân chia lại có một tiếng chuông rất êm tai và bộ
đếm số tiếng chuông ở tâm bánh xe lại tăng thêm 1. Để bắt
đầu quay bánh xe người ta phải lắc nhẹ nó rồi mới quay được
và khi lắc bộ đếm trở về 0.
Steve rất thích thú và chơi rất nhiều lần. Steve không bao giờ
ghi các chữ cái trùng nhau trên vành bánh xe.
Thông thường, với một bộ các chữ cái ghi trên vành bánh xe
Steve chơi k lần liên tiếp, sau mỗi lấn quay Steve ghi lại số
hiển thị ở tâm và chữ cái mà mũi tên chỉ tới.
Điều đó diễn ra đã lâu lắm rồi. Cây cối trong công viên thành
phố đã nhiều lần thay lá. Chiếc đồ chơi xinh xắn đã thất lạc đâu đó. Một lần tình cờ Steve tìm thấy
mảnh giấy cũ ghi lại kết quả chơi. Những kỷ niệm đẹp của thời thơ ấu êm dịu, vô tư lự chợt trào lên
như những đợt sóng lừng và Steve không cưỡng nỗi ý nghĩ phải khôi phục lại dòng chữ đã viết trên
bánh xe. Tuy vậy, cũng không loại trừ khả năng do vội vàng, kết quả đã bị ghi sai (ai mà ngờ được
mẫu giấy này lại có giá trị như vậy trong tương lai!) do đó không xác định được những gì đã ghi trên
bánh xe. Ngoài ra, kết quả lần chơi này cũng có thể khá nghèo nàn, không đủ thông tin để xác định
hết các chữ cái đã ghi trên bánh xe.
Yêu cầu: Cho n, k và các giá trị mi, ci, trong đó mi là số ghi được ở lượt quay thứ i, ci – ký tự kim
chỉ ở lượt quay này (2 ≤ n ≤ 25, 1 ≤ k ≤ 100, i = 1 ÷ k). Hãy xác định dòng chữ ghi trên vành bánh
xe kể từ ký tự lần cuối cùng được kim chỉ tới. Nếu thông tin mâu thuẫn thì đưa raxâu chỉ chứa một ký
tự “!”. Nếu một số ký tự không đủ thông tin để xác định thì vào vị trí đó trong xâu đưa ra ký tự “?”.
Dữ liệu: Vào từ file văn bản WHEEL.INP:
Dòng đầu tiên chứa 2 số nguyên n và k,
Dòng thứ i trong k dòng sau chứa 2 giá trị mi và ci.
Kết quả: Đưa ra file văn bản WHEEL.OUT xâu xác định được.
Ví dụ:
WHEEL.INP WHEEL.OUT 8 8
4 V
3 I
7 T
7 A
6 R
5 N
1 O
9 H
HONITAVR
O
H
N
IT
AV
R
9
C++
29/64
C++ #include <fstream>
#include <iostream>
#include <string>
using namespace std;
string s,s2;
char c;
long n,k,m,fl,v;
ifstream fi ("WHEEL.INP"); ofstream fo ("WHEEL.OUT");
void chbi()
{
fi>>n>>k;
s=""; fl=0;v=n-1;
for (int i = 0; i<n; i++) s+='?';
}
void xldong()
{ fi>>m>>s2;
c=s2[0];
m%=n;
v-=m;
if (v<0) v+=n;
if (s[v]=='?') s[v]=s2[0];
else
if (s[v]!= c) {fl=1; return;}
}
void gnkq()
{ int i;
if (fl==1) s='!';
else
{i=s.find(c); s+=s;
s=s.substr(i,n);
}
fo <<s;
}
int main()
{
chbi();
for (int i=0; i<k; i++) xldong();
gnkq();
fi.close(); fo.close();
}
30/64
PASCAL Program WHEEL;
Const tfi = 'WHEEL.INP';
tfo = 'WHEEL.OUT';
Var n,k,m,i,err,v:integer;
c0,c:char;
s:string[50];
fi,fo:text;
BEGIN
assign(fi,tfi); reset(fi);
readln(fi,n,k);
s:=''; err:=0; v:=n;
for i:= 1 to n do s:=s+'?';
for i:=1 to k do
begin
readln(fi,m,c0,c);
v:=v-m; if v<=0 then v:=v+n;
if ((s[v]<>'?') and (s[v]<>c)) then
begin err:=1; s:='!'; break end;
s[v]:=c
end;
assign(fo,tfo); rewrite(fo);
if err=0 then
begin s:=s+s; s:=copy(s,v,n) end;
write(fo,s);
close(fi); close(fo)
END.
31/64
GHI TA Tên chương trình: GUITAR.???
Trong cuộc thi sáng tạo rô bốt Steve đã trình diễn rô bốt chơi ghi ta của mình, một rô bốt,
theo lời mô tả của tác giả “có đến hàng tỷ ngón tay”!
Đàn ghi ta có 6 dây đánh số từ 1 đến 6 và có p phím đánh số từ
1 đến p. Khi chơi nhạc người ta gẩy dây và bấm phím để có các
giai điệu khác nhau. Với mỗi dây, nếu có nhiều phím cùng
được bấm thì âm điệu sẽ được quyết định bởi phím có số cao
nhất. Ví dụ, âm điệu khi gẩy dây số 3 và bấm đồng thời các
phím 5 và 7 sẽ giống như khi ta gẩy dây này và chỉ bấm phím
số 7.
Điều khó khăn nhất khi biểu diễn một bài nhạc là điều khiển
cường độ tức phải gẩy dây nào và bấm phím nào, còn điều
khiển trường độ (thời gian phát một nốt) không phải là quá khó
về phương diện kỹ thuật. Chương trình điều khiển cường độ
của Steve được tối ưu hóa theo hướng giảm số lần chuyển tay
bấm phím xuống còn ít nhất.
Xét bản nhạc có n nốt, nốt thứ i được cho bởi cặp giá trị
nguyên (si, fi), trong đó si – dây cần gẩy và fi – phím cần
bấm (1 ≤ si ≤ 6, 1 ≤ fi ≤ p, i = 1 ÷ n). Bản nhạc cần chơi
theo trình tự thực hiện lần lượt từ nốt 1 đến nốt n. Ví dụ, với n
= 5, p = 15 và bản nhạc là (2, 8), (2, 10), (2, 12), (2, 10) và
(2,5), thao tác bấm phím của rô bốt là bấm phím 8, bấm phím
10, bấm phím 12, nhả phím 12, nhả phím 10, nhả phím 8 và
bấm phím 5, tất cả là 7 thao tác, thực hiện trên dây số 2.
Yêu cầu: Cho n, p và các si, fi (1 ≤ n ≤ 5×105, 2 ≤ p ≤ 3×10
5, i = 1 ÷ n). Hãy xác định số
thao tác bấm phím ít nhất cần thực hiện.
Dữ liệu: Vào từ file văn bản GUITAR.INP:
Dòng đầu tiên chứa 2 số nguyên n và p,
Dòng thứ i trong n dòng sau chứa 2 số nguyên si và fi.
Kết quả: Đưa ra file văn bản GUITAR.OUT một số nguyên – số thao tác bấm phím ít nhất
cần thực hiện.
Ví dụ:
GUITAR.INP GUITAR.OUT 5 15
2 8
2 10
2 12
2 10
2 5
7
e11 Cr7 3
32/64
C++ #include <fstream>
using namespace std;
int main()
{long n,p,s,f,k,r;
ifstream fi ("GUITAR.INP");
ofstream fo ("GUITAR.OUT");
fi>>n>>p;
{long a[6][p];
for (int i=0; i<6;i++) a[i][0]=0;
r=0; k=0;
for (int i=1; i<=n;i++)
{ fi>>s>>f; --s;
while (a[s][k]>f)
{ --k; ++r;}
if (a[s][k]<f) { ++k; a[s][k]=f; ++r;}
}
}
fo<<r;
fi.close(); fo.close();
}
PASCAL Program GUITAR;
Const tfi='GUITAR.INP';
tfo='GUITAR.OUT';
var m,n,p,k,s,f,i,r:longint;
a:array[1..6,0..300000] of integer;
fi,fo:text;
BEGIN
assign(fi,tfi); reset(fi);
readln(fi,n,p);
r:=0; k:=0;
for i:=1 to 6 do a[i,0]:=0;
for i :=1 to n do
begin
readln(fi,s,f);
while a[s,k]>f do begin dec(k); inc(r) end;
if a[s,k]<f then begin inc(k);a[s,k]:=f; inc(r)
end
end;
assign(fo,tfo); rewrite(fo);
writeln(fo,r);
close(fi); close(fo)
END.
33/64
Ô MÀU VÀNG Tên chương trình: YELLOW.???
Phòng ngủ của Gholam được lát bằng các viên gạch men vuông màu vàng và trắng kẻ ca rô.
Mỗi khi buồn chán Gholam đứng ở một viên gạch màu trắng nào đó, quay mặt sang phải
hoặc sang trái, nghĩ trong đầu một số nguyên n rồi đi thẳng theo hàng đó n bước, mỗi bước
Gholam chuyển sang viên gạch kề ở
cùng hàng, nếu chạm tường thì quay
1800 và tiếp tục thực hiện các bước đi,
vừa đi vừa đếm số lần mình đặt chân
lên viên gạch màu vàng. Các viên
gạch trong hàng mà Gholam đứng
được đánh số từ 1 đến m từ trái qua
phải. Ví dụ, m = 6, màu các viên gạch từ trái qua phải là Y, W, W, Y, W, Y (Y – màu vàng, W –
màu trắng), Gholam đứng ở viên gạch số 3, đi 7 bước với hướng ban đầu là sang phải, trong
trường hợp này Gholam sẽ dừng lại ở viên số 2 và có 3 lần bước lên viên gạch màu vàng.
Dữ liệu: Vào từ file văn bản YELLOW.INP:
Dòng đầu tiên chứa số nguyên t – số lượng tests trong file,
Mỗi test cho trên 2 dòng:
o Dòng đầu tiên chứa 2 số nguyên m và n (3 ≤ m ≤ 100, 1 ≤ n ≤ 1 000),
o Dòng thứ 2 chứa m số nguyên a1, a2, . . ., am, ai = 0 ứng với viên màu vàng,
ai > 0 ứng với viên màu trắng, ai = 2 có nghĩa là Gholam đang đứng ở viên
thứ i và quay mặt về bên phải (nhìn xuống cuối hàng), ai = 3 xác định
Gholam đang đứng ở viên thứ i và quay mặt về bên trái (nhìn về đầu hàng
ghạch). Dữ liệu đảm bảo chỉ có một ai > 1.
Kết quả: Đưa ra file văn bản YELLOW.OUT, kết quả mỗi test đưa ra trên một dòng dưới
dạng số nguyên – số lần bước trên viên gạch màu vàng.
Ví dụ:
YELLOW.INP YELLOW.OUT 2
6 7
0 1 2 0 1 0
5 3
0 3 1 0 0
3
1
34/64
C++ #include <fstream>
using namespace std;
ifstream fi ("yellow.inp");
ofstream fo ("yellow.out");
int r;
void xly()
{int m,n,r,t0,t1,d,id,m2,p,q;
fi>>m>>n;
{int a[3*m-2];
t0=0;t1=0;m2=2*m-2;
for (int i=m-1;i<2*m-1;i++)
{fi>>a[i];
switch (a[i])
{case 0: t0++; break;
case 1: t1++; break;
case 2:case 3: d=a[i]; id=i; a[i]=0;break;
}
}
for (int i=0;i<m-1;i++) a[i]=a[2*m-2-i];
for (int i=0;i<m-1;i++) a[i+2*m-1]=a[2*m-3-i];
t1*=2; t-=(a[m-1]+a[2*m-2]);
p=n/m2; q=n%m2;
r=t1*p;
if (d==2) for(int i=1; i<=q;i++) r+=a[i+id];
else for (int i=1;i<=q;i++) r+=a[id-i];
fo<<r<<endl;
}
}
int main()
{int t;
fi>>t;
for (int i =1;i<=t;i++) xly();
fi.close(); fo.close();
}
35/64
PASCAL Program YELLOW;
Const tfi='YELLOW.INP';
tfo='YELLOW.OUT';
var t,m,n,p,q,t1,m2,i,r,d,id:longint;
a:array[-99..199] of integer;
fi,fo:text;
Procedure xly;
var i:integer;
Begin
readln(fi,m,n); t1:=0; m2:=2*m-2;
for i:=1 to m do
begin
read(fi,a[i]);
case a[i] of
1:inc(t1);
2,3:begin id:=i;d:=a[i];a[i]:=0 end
end
end;
for i:=2 to m do a[2-i]:=a[i];
for i:=2 to m do a[m+i-1]:= a[m-i+1];
t1:=t1*2-a[1]-a[m];
p:=n div m2; q:=n mod m2;
r:=t1*p;
if d=2 then for i:=id to id+q do r:=r+a[i]
else for i:=id downto id-q do r:=r+a[i];
writeln(fo,r)
End;
36/64
TI VI Tên chương trình: TV.???
Người ta tiến hành điều tra thống kê tình hình xem ti vi trong dân chúng. Mỗi người trong số
n người được hỏi yêu cầu cho biết thời điểm người ta bắt đầu xem ti vi và thời điểm mà hết
cuối giây đó người ta rời khỏi ti vi. Câu trả lời được ghi lại dưới dạng:
HH:MM:SS – HH:MM:SS
Trong đó 0 ≤ HH ≤ 23, 0 ≤ MM, SS ≤ 59. Hai thời điểm đầu và cuối có thể không cùng một
ngày, ví dụ, một người có thể bắt đầu xem từ 23:45:30 cho đến 01:15:00 sáng ngày hôm sau.
Dĩ nhiên, không ai xem ti vi liên tục cả một ngày.
Sau khi đã có đủ dữ liệu, các nhà thống kê bắt đầu phân tích.
Độ phổ biến của một giây nào đó được tính bằng tổng số người có xem ti vi ở giây đó. Độ
phổ biến của một khoảng thời gian được tính bằng tổng độ phổ biến của các giây trong
khoảng đó chia cho tổng số giây trong khoảng.
Hãy tính độ phổ biến của mỗi khoảng trong số q khoảng cho trước mà các nhà thống kê quan
tâm.
Dữ liệu: Vào từ file văn bản TV.INP:
Dòng đầu tiên chứa số nguyên n (1 ≤ n ≤ 105),
Mỗi dòng trong n dòng sau chứa một câu trả lời của người được hỏi,
Dòng thứ n+2 chứa số nguyên q (1 ≤ q ≤ 105),
Mỗi dòng trong q dòng sau chứa một khoảng thời gian thống kê, ghi theo quy cách
như câu trả lời khi điều tra.
Kết quả: Đưa ra file văn bản TV.OUT, kết quả ứng với mỗi khoảng thống kê đưa ra trên một
dòng dưới dạng số thực độ chính xác không ít hơn 10-6
.
Ví dụ:
TV.INP TV.OUT 5
00:00:00 - 00:00:01
00:00:01 - 00:00:03
00:00:00 - 00:00:02
00:00:05 - 00:00:09
00:00:06 - 00:00:06
5
00:00:00 - 00:00:03
00:00:07 - 00:00:09
00:00:06 - 00:00:06
00:00:05 - 00:00:09
00:00:00 - 00:00:09
2.0000000000
1.0000000000
2.0000000000
1.2000000000
1.4000000000
e25 Cr11 2
C++
Thời điểm bắt đầu Thời điểm cuối
37/64
#include <fstream>
#include <string>
#include <iomanip>
using namespace std;
long n,q,b[86400],tb,te;
string s;
ifstream fi ("TV.INP");
ofstream fo ("TV.OUT");
void nhapdl()
{ s.reserve(19);
getline(fi,s);
tb=(((s[0]-48)*10+s[1]-48)*60+(s[3]-48)*10+s[4]-48)*60+(s[6]-
48)*10+s[7]-48;
te=(((s[11]-48)*10+s[12]-48)*60+(s[14]-48)*10+s[15]-48)*60+(s[17]-
48)*10+s[18]-48;
}
void gn_be()
{b[tb]++;
if (te==86399) return;
if (te<tb)b[0]++;
b[++te]--;
}
void tichluy()
{long t;
t=b[0];
for (int i=1;i<86400;i++)
{ t+=b[i];
b[i]=(b[i-1]+t);
}
}
float xd_r()
{ float x;
x=b[te];
if (tb==0) return(x);
if (tb<=te) return(x-b[tb-1]);
return(x+b[86399]-b[tb-1]);
}
void xd_kq()
{float r,d;
if (tb>te) d=86400-tb+te+1; else d=te-tb+1;
r= xd_r()/d;
fo<<setprecision(8)<<r<<endl;
}
int main()
{memset(b,0,sizeof(b));
fi>>n; getline(fi,s);
for (int i=1;i<=n;i++)
{nhapdl();
gn_be();
}
tichluy();
fi>>q; getline(fi,s);
38/64
for (int i=1;i<=q;i++)
{nhapdl();
xd_kq();
}
fi.close(); fo.close();
}
ĐƢỜNG GẤP KHÚC Tên chương trình: POLYGON.???
Trên lưới ô vuông vô hạn với tọa độ các đỉnh nút là nguyên người ta cho một đường gấp khúc
đơn khép kín n đỉnh, tức là đường gấp khúc mà 2 cạnh liên tiếp chỉ có một điểm chung ở
đỉnh, một cạnh không cắt các cạnh khác và cũng
không có điểm chung nào khác. Đỉnh của đường gấp
khúc trùng với điểm chia của lưới.
Hãy tính tổng độ dài các đoạn thẳng của lưới nằm gọn
trong đa giác giới hạn bởi đường gấp khúc.
Dữ liệu: Vào từ file văn bản POLYGON.INP:
Dòng đầu tiên chứa số nguyên n (3 ≤ n ≤
105),
Dòng thứ i trong n dòng sau chứa 2 số nguyên
xi và yi – tọa độ đỉnh i (|xi|, |yi| ≤ 5×108).
Tọa độ các đỉnh cho theo một chiều nào đó
(cùng hoặc ngược chiều kim đồng hồ).
Kết quả: Đưa ra file văn bản POLYGON.OUT một số
thực L – tổng độ dài tìm được. Gọi R là độ dài chính
xác. Kết quả được coi là đúng nếu thỏa mãn một trong 2 điều kiện sau:
|L-R| ≤ R×10-6
(sai số tương đối),
|L-R| ≤ 10-6
(sai số tuyệt đối).
Ví dụ:
POLYGON.INP POLYGON.OUT 5
0 0
-2 2
-2 -1
2 -2
2 0
12.5
39/64
Giải thuật
Đa giác có thể chia thành các hình thang có cạnh song song với trục Oy, chiều cao bàng 1 (có
thể có hình thang suy biến thành tam giác),
Các đoạn thẳng của lưới, song song với trục Oy và nằm gọn trong đa giác, tham gia vào quá
trình tính diện tích 2 lần: một lần là
đáy dưới và lần khác - là đáy trên.
Các đoạn thẳng của lưới, song song
với trục Oy và nằm đường biên –
tham gia một lần.
Có nhận xét tương tự như vậy với các
đoạn của lưới, song song với trục Ox
và nằm gọn trong đa giác.
Gọi S là diện tích đa giác, lv – tổng
độ dài các đoạn biên song song với
Oy, lh – tổng độ dài các đoạn biên song song với Ox, r là kết quả cần tìm,ta có:
2r = s – 2
lv + s –
2
lh,
trong đó s = |)(|2
11
1
1 ii
n
i
ii yxyx
, xi+1 = x1, yi+1 = y1.
40/64
C++
#include <iostream>
#include <fstream>
#include <cmath>
using namespace std;
int n, lh = 0, lv = 0, S = 0, x[100100],
y[100100];
ifstream fi ("POLYGON.INP");
ofstream fo ("POLYGON.OUT");
void input(){
fi>>n;
for (int i = 1; i <= n; i++) fi>>x[i]>>y[i];
x[n+1] = x[1];
y[n+1] = y[1];
}
void cal_area(){
for (int i = 1; i <= n; i++)
S += x[i]*y[i+1] - y[i]*x[i+1];
S = abs(S);
}
void cal_lh_lv(){
for (int i = 1; i <= n; i++){
if (x[i] == x[i+1])
lv += abs(y[i+1] - y[i]);
if (y[i] == y[i+1])
lh += abs(x[i+1] - x[i]);
}
}
void output(){
fo << double(2*abs(S) - lv - lh) / double(2);
}
int main(){
input();
cal_area();
cal_lh_lv();
output();
return 0;
}
41/64
C++ Vào ra theo quy cách
#include <iostream>
#include <cmath>
using namespace std;
int n, lh = 0, lv = 0, S = 0, x[100100], y[100100];
void input(){
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d %d", &x[i], &y[i]);
x[n+1] = x[1];
y[n+1] = y[1];
}
void cal_area(){
for (int i = 1; i <= n; i++)
S += x[i]*y[i+1] - y[i]*x[i+1];
S = abs(S);
}
void cal_lh_lv(){
for (int i = 1; i <= n; i++){
if (x[i] == x[i+1])
lv += abs(y[i+1] - y[i]);
if (y[i] == y[i+1])
lh += abs(x[i+1] - x[i]);
}
}
void output(){
cout << double(2*abs(S) - lv - lh) / double(2);
}
int main(){
freopen("POLYGON.INP", "r", stdin);
freopen("POLYGON.OUT", "w" ,stdout);
input();
cal_area();
cal_lh_lv();
output();
return 0;
}
42/64
QUAY CÓP Tên chương trình: PLAGIARY.???
Thí sinh tham dự Thi lập trình thế giới nộp n files lời giải f1, f2, . . ., fn. Trước khi chấm
Ban Giám khảo quyết định kiểm tra xem có trường hợp chép bài của nhau không. Người ta
lấy từng cặp 2 files, so sánh nội dung xem chúng có quá giống nhau hay không.
Nhưng số lượng files là rất lớn và không đủ thời gian so sánh tất cả các cặp files. Vì vậy,
người ta chỉ so sánh các file có kích thước gần nhau.
Cụ thể là, nếu với 2 files, kích thước file bé nhỏ hơn 90% kích thước file lớn thì sẽ không só
sánh chúng. Nói một cách khác, gọi di là kích thước file fi, người ta sẽ so sánh 2 files fi và
fj nếu di ≤ dj, di ≥ 0.9×dj, i ≠ j.
Hãy xác định số lượng cặp files cần so sánh.
Dữ liệu: Vào từ file văn bản PLAGIARY.INP:
Dòng đầu tiên chứa số nguyên n (1 ≤ n ≤ 105),
Dòng thứ 2 chứa n số nguyên d1, d2, . . ., dn (1 ≤ di ≤ 108 , i = 1 ÷ n).
Kết quả: Đưa ra file văn bản PLAGIARY.OUT một số nguyên – số lượng cặp files cần so
sánh.
Ví dụ:
PLAGIARY.INP PLAGIARY.OUT 5
1 1 1 1 1
10
Giải thuật
Sắp xếp {di} theo thứ tự tăng dần của giá trị,
Với mỗi i từ 1 đến n-1 tìm k = max{j: j>=i, di ≥ 0.9dj},
Nếu với i đã có k, với i+1 – chỉ cần tìm tiếp từ k+1 trở đi,
Với mỗi i kết quả tăng lên một lượng là k – i.
Độ phức tạp: O(nlogn).
43/64
C++
#include <iostream>
#include <ctime>
using namespace std;
int n, up, res = 0, a[100100];
int main(){
freopen("PLAGIARY.INP", "r", stdin);
freopen("PLAGIARY.OUT", "w", stdout);
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
sort(a, a+n);
a[n] = INT_MAX;
for (int i = 0; i < n; i++){
int j = min(n - 1, up);
while (j < n && 9*a[j] <= 10*a[i])
j++;
up = j;
res += j-i-1;
}
printf("%d", res);
return 0;
}
44/64
CỬ TẠ Tên chương trình: DUMB_BELL.???
Rèn luyện thể lực bằng cách tập nâng tạ thu hút được sự chú ý của rất nhiều bạn trẻ. Tạ là
một thanh trục có gắn ở hai đầu các đĩa tạ. Bộ đĩa tạ trong phòng tập bao gồm các loại 1kg,
2kg, 5kg, 10kg, 15kg và 20kg với số lượng mỗi loại là đủ nhiều. Các đĩa tạ ở hai đầu thanh
được gắn đối xứng để đảm bảo thanh tạ được cân. Mỗi người, tùy theo thể lực của mình, lắp
các đĩa tạ để có trọng lượng phù hợp. Để điều chỉnh trọng lượng, người ta tháo các đĩa ngoài
cùng, lắp các đĩa mới vào. Do tính đối xứng của
thanh tạ, ta chỉ xét các thao tác điều chỉnh ở một
đầu.
Hiện tại ở một đầu đang có n đĩa tạ gắn vào trục (1
≤ n ≤ 10), tính từ trong ra ngoài đĩa thứ i có trọng
lượng pi. Bạn cần có thanh tạ với trọng lượng một
đầu là w (0 ≤ w ≤ 100). Ví dụ, hiện tại n = 4 và các
đĩa tạ là (2, 2, 1, 20), bạn cần điều chỉ trọng lượng
thành 14kg. Bạn sẽ phải thực hiện 3 thao tác tháo
lắp: tháo đĩa 20kg, tháo đĩa 1kg và lắp đĩa 10kg.
Yêu cầu: Cho n, pi, i=1 ÷ n, w. Hãy xác định số thao tác ít nhất cần thực hiện.
Dữ liệu: Vào từ file văn bản DUMB_BELL.INP:
Dòng đầu tiên chứa số nguyên n,
Dòng thứ 2 chứa n số nguyên p1, p2, . . ., pn,
Dòng thứ 3 chứa số nguyên w.
Kết quả: Đưa ra file văn bản DUMB_BELL.OUT một số nguyên – số thao tác ít nhất cần
thực hiện.
Ví dụ:
DUMB_BELL.INP DUMB_BELL.OUT
4
2 2 1 20
14
3
2 2 1
20
45/64
C++ #include <iostream>
using namespace std;
const int b[6] = {1, 2, 5, 10, 15, 20};
int n, w, p, F, res = INT_MAX, s[11], L[201];
int main(){
freopen("DUMB_BELL.INP", "r", stdin);
freopen("DUMB_BELL.OUT", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++){
scanf("%d", &p);
s[i] = s[i-1] + p;
}
scanf("%d", &w);
L[1] = L[2] = L[5] = L[10] = L[15] = L[20] = 1;
for (int i = 1; i <= 200; i++){
if (L[i] == 1)
continue;
L[i] = INT_MAX;
for (int j = 0; j < 6 && b[j] < i; j++)
L[i] = min(L[i], L[i - b[j]] + 1);
}
for (int i = 0; i <= n; i++){
F = n-i;
if (s[i] > w)
F = INT_MAX;
else if (s[i] < w)
F += L[w - s[i]];
res = min(res, F);
}
printf("%d", res);
return 0;
}
46/64
TỌA ĐỘ VÙNG Tên chương trình: REG_COOR.???
Để mau chóng tìm kiếm được các vùng và cung cấp bản đồ tương ứng cho người dùng
Google Map áp dụng cơ chế cây tứ phân. Bản đồ toàn bộ trái đất là t, được coi như một hình
vuông. Bằng các đường thẳng song song với cạnh và đi qua điểm giữa của cạnh, bản đồ ban
đầu được chia thành 4 phần bằng nhau, mỗi phần bằng
bản đồ ban đầu và ký hiệu các phần
đó là t, s, r, q theo chiều ngược kim đồng hồ, bắt đầu từ phần dưới trái. Đường dẫn truy
nhập tới các phần này sẽ là tt, ts, tr và tq. Mỗi phần nhận được lại có thể chia thành 4
phần con, đánh số như trên, đường dẫn tới phần con này sẽ có độ dài 3 và cứ như thế tiếp tục
cho đến khi đạt được mục tiêu tìm kiếm. Ví dụ, đường dẫn tới bản đồ Việt Nam là trst.
Xét bản đồ số hình vuông tọa độ góc dưới trái là (0, 0), góc trên phải là (2n, 2n) với 0 < n ≤
1000. Với điểm P tọa độ (x,y) (0 < x, y < 2n ta có thể chỉ ra
đường dẫn tới vùng nhỏ nhất chứa P như một điểm trong
(không nằm trên biên). Ví dụ, với P có tọa độ (5, 3) và n = 3,
vùng nhỏ nhất chứa P có đường dẫn là tsq.
Yêu cầu: Cho n, x, y. Hãy xác định đường đẫn tới vùng chứa
điểm (x, y).
Dữ liệu: Vào từ file văn bản REG_COOR.INP gồm một dòng
chứa 3 số nguyên n, x và y.
Kết quả: Đưa ra file văn bản REG_COOR.OUT xâu đường
dẫn.
Ví dụ:
REG_COOR.INP REG_COOR.OUT 3 5 3 tsq
tt
tq tr
tsq
47/64
C++ #include <iostream>
#include <string>
using namespace std;
int n;
string x, y, res;
string div(string a, int b){
int hold = 0, s = 0;
string ans = "";
for (int i = 0; i < a.size(); i++){
hold = 10 * hold + (a[i] - '0');
s = hold / b;
hold %= b;
ans += ('0' + s);
}
while (ans[0] == '0')
ans.erase(0, 1);
return ans;
}
string binary_representation(string a){
string ans = "";
while (a.size()){
ans += ('0' + (a[a.size()-1] - '0') %
2);
a = div(a, 2);
}
for (int i = 0; i < (ans.size() >> 1); i++)
swap(ans[i], ans[ans.size()-i-1]);
while (ans.size() < n)
ans.insert(0, "0");
return ans;
}
int main(){
freopen("REG_COOR.INP", "r", stdin);
freopen("REG_COOR.OUT", "w", stdout);
cin >> n >> x >> y;
x = binary_representation(x);
y = binary_representation(y);
res = "";
for (int i = 0; i < n-1; i++){
if (x[i] == '0' && y[i] == '0')
res += 't';
else if (x[i] == '0' && y[i] == '1')
res += 'q';
else if (x[i] == '1' && y[i] == '0')
res += 's';
else if (x[i] == '1' && y[i] == '1')
res += 'r';
if (min(x.rfind("1"), y.rfind("1")) < i-1)
break;
}
cout << "t" << res;
return 0;
}
48/64
BỘ TỘC Tên chương trình: CLAN.???
Có một hòn đảo rất đẹp, thu hút nhiều khách du lịch ghé thăm. Trên đảo có n người thuộc
nhiều bộ tộc sinh sống. Dân cư trên đảo rất thân thiện. Mỗi người thuộc một bộ tộc nào đó.
Trong đoàn du lịch có một nhà nhân chủng học. Tranh thủ dịp may được ghé thăm đảo ông
không bỏ phí thời gian tiến hành khảo sát. Ông gặp từng người một trên đảo với một câu hỏi
duy nhất: “Trên đảo, bộ tộc của bạn có bao nhiêu người?”. Từ kết quả khảo sát, ông đã xác
định được số bộ tộc khác nhau tồn tại trên đảo.
Ví dụ, với n = 10 và các câu trả lời là 5, 1, 2, 5, 5, 2, 5, 5, 2, 2 ta có thể suy ra là trên hòn đảo
có 4 bộ tộc khác nhau.
Yêu cầu: Cho n và các câu trả lời. Hãy xác định số bộ tộc trên đảo. Dữ liệu đảm bảo bài toán
có nghiệm.
Dữ liệu: Vào từ file văn bản CLAN.INP:
Dòng đầu tiên chứa số nguyên n (1 ≤ n ≤ 105),
Mỗi dòng trong n dòng sau chứa một số nguyên – câu trả lời nhận được.
Kết quả: Đưa ra file văn bản CLAN.OUT một số nguyên – số bộ tộc trên đảo.
Ví dụ:
CLAN.INP CLAN.OUT
10
5
1
2
5
5
2
5
5
2
2
4
C++
#include <fstream>
#include <set>
using namespace std;
int n, ans, res = 0;
set <int> a;
multiset <int> s;
int main(){
ifstream finp ("CLAN.INP");
ofstream fout ("CLAN.OUT");
finp >> n;
while (n--){
finp >> ans;
a.insert(ans);
s.insert(ans);
}
for (set <int>::iterator i = a.begin(); i !=
a.end(); i++)
res += s.count(*i) / (*i);
fout << res;
}
49/64
LÂY NHIỄM Tên chương trình: INFECT.???
Steve phát hiện ra một lỗi trong hệ điều hành nỗi tiếng đang được sử dụng phổ biến. Lợi dụng
lỗi này ta có thể truy nhập và điều khiển máy tính bất kỳ đang kết nối và sử dụng hệ điều
hành này. Khi kích hoạt cơ chế tạo lỗi, nếu một máy tính đang được kết nối với đúng 8 máy
tính khác ta có một con đường riêng để tuy nhập vào nó trong đúng 8 giây.
Steve viết một chương trình virus đơn giản khai thác lỗi này. Virus hoạt động theo kiểu tuần
tự, nghĩa là máy tính bị nhiễm virus ở mỗi thời điểm chỉ tấn công sang đúng một máy khác
mà thôi.
Yêu cầu: Ban đầu Steve có 8 mấy đã bị nhiễm virus. Hãy xác định sau bao lâu virus sẽ lan
được tới tất cả n máy tính của Steve.
Dữ liệu: Vào từ file văn bản INFECT.INP gồm một dòng chứa số nguyên n (8 ≤ n ≤108).
Kết quả: Đưa ra file văn bản INFECT.OUT một số nguyên – thời gian cần thiết để các máy
đều bị nhiểm virus.
Ví dụ2:
INFECT. INP INFECT. OUT
20 80
C++ #include <fstream>
using namespace std;
int n, cur = 8, res = 0;
int main(){
ifstream finp ("INFECT.INP");
ofstream fout ("INFECT.OUT");
finp >> n;
while (cur < n){
cur += (cur / 8);
res += 8;
}
fout << res;
return 0;
}
50/64
MẬT KHẨU Tên chương trình: PASSW.???
Để có được một mật khẩu có độ an toàn cao cần phải khéo léo trong cách chọn. Người ta
thường hay dùng các mật khẩu đơn giản, dễ nhớ. Nhưng những mật khẩu đó không có độ an
toàn cao. Cần phải có một chiến lược chọn mật khẩu.
Xét chiến lược chọn mật khẩu sau:
Mật khẩu phải có độ dài ít nhất 6 ký tự,
Trong mật khẩu phải bao gồm các ký tự 3 loại:
o Chữ cái latinh in hoa,
o Chữ cái latinh thường,
o Chữ số hệ 10.
Yêu cầu: Cho xâu S độ dài không quá 105 và chỉ chứa các ký tự thuộc 3 loại dã nêu ở
trên.Hãy xác định độ dài của xâu con ngắn nhất các ký tự liên tiếp nhau có thể sử dụng làm
mật khẩu theo chiến lược đã nêu. Nếu không tồn tại xâu con thỏa mãn thì đưa ra kết quả độ
dài 0.
Dữ liệu: Vào từ file văn bản PASSW.INP:
Dòng đầu tiên chứa số nguyên n – số lượng tests (1 ≤ n ≤ 50),
Mỗi dòng trong n dòng sau chứa một xâu S.
Kết quả: Đưa ra file văn bản PASSW.OUT n số nguyên, mỗi số trên một dòng, dòng i tương
ứng với kết quả test thứ i.
Ví dụ:
PASSW.INP PASSW.OUT
4
AliKam123test
AbCdEfG
88syadneerG
Windows7released21october2009
6
0
10
8
51/64
C++ #include <fstream>
#include <string>
using namespace std;
int n;
int main(){
ifstream finp ("PASSW.INP");
ofstream fout ("PASSW.OUT");
finp >> n;
while (n--){
string s;
finp >> s;
if (s.size() < 6){
fout << "0" << endl;
continue;
}
int upper = -1 , lower = -1, num = -1, res = INT_MAX;
for (int i = 0; i <= s.size(); i++){
if ('A' <= s[i] && s[i] <= 'Z')
upper = i;
else if ('a' <= s[i] && s[i] <= 'z')
lower = i;
else if ('0' <= s[i] && s[i] <= '9')
num = i;
if (upper >= 0 && lower >= 0 && num >=0)
res = min(res, i - min(min(upper, lower), num) + 1);
}
if (upper == -1 || lower == -1 || num == -1)
fout << "0" << endl;
else
fout << max(res, 6) << endl;
}
return 0;
}
52/64
TRỢ GIÚP ĐĂNG NHẬP Tên chương trình: F1LOGIN.???
Cuối cùng thì một hệ thống chấm điểm mới cũng đã được đưa vào sử dụng. Mật khẩu để truy
cập phải khác rỗng, có độ dài không quá 10 ký tự bao gồm chữ số, chữ cái la tinh thường và
hoa. Mô đun đăng nhập, ngoài việc kiểm tra mật khẩu còn giúp người sử dụng phát hiện hai
loại sai sót thường gặp:
Đặt chế độ phím CAPLOCK ở chế độ ON, khi đó chữ hoa có thể thành chữ thường và
ngược lại,
Đặt phím NUMLOCK ở chế độ OFF, khi đó các ký tự số sẽ bị bỏ qua.
Chương trình sẽ so sánh mật khẩu đã đăng ký với mật khẩu nhập vào. Nếu 2 mật khẩu trùng
khớp với nhau thì đưa ra thông báo “Login successful.”. Nếu ở mật khẩu nhập vào,
thãy mỗi chữ cái hoa thành chữ cái thường tương ứng và thay mỗi chữ cái thường thành chữ
cái hoa tương ứng sẽ được mật khẩu đúng, khi đó chương trình đưa ra thông báo ”Wrong
password. Please, check your caps lock key.” Nếu trong mật khẩu hệ
thống lưu trữ bỏ tất cả các ký tự số, kết quả trùng khớp với xâu người sử dụng nhập thì đưa ra
thông báo “Wrong password. Please, check your num lock key.” Nếu
trong mật khẩu hệ thống lưu trữ bỏ tất cả các ký tự số và đổi chữ hoa thành chữ thường, chữ
thường thành chữ hoa, kết quả trùng khớp với xâu người sử dụng nhập thì đưa ra thông báo
“Wrong password. Please, check your caps lock and num lock
key.” Trong các trường hợp còn lại – đưa ra thông báo “Wrong password.”.
Dữ liệu: Vào từ file văn bản F1LOGIN.INP:
Dòng đầu tiên chứa số nguyên t – số lượng tests,
Mỗi dòng trong t dòng tiếp theo chứa s xâu cách nhau bởi dấu cách: mật khẫu lưu trữ
và mật khẩu nhập vào.
Kết quả: Đưa ra file văn bản F1LOGIN.OUT các thông báo tương ứng với từng cặp mật
khẩu, mỗi thông báo trên một dòng.
Ví dụ:
F1LOGIN.INP F1LOGIN.OUT 6
aaBBccDD aaBBccDD
aaBBccDD aaBBccDD9
aaBBccDD aaBBCCDD
aaBBccDD AAbbCCdd
a4B3c2D1 aBcD
a4B3c2D1 AbCd
Login successful Wrong password. Wrong password. Wrong password. Please, check your caps lock key.
Wrong password. Please, check your num lock key.
Wrong password. Please, check your caps lock and num lock key.
53/64
C++ #include <iostream>
#include <string>
using namespace std;
int n;
int main(){
freopen("F1LOGIN.INP", "r", stdin);
freopen("F1LOGIN.OUT", "w", stdout);
cin >> n;
while (n--){
string s,t;
cin >> s >> t;
if (s.compare(t) == 0){
cout << "Login successful. " << endl;
continue;
}
cout << "Wrong password. ";
string ss = s, tt = t;
bool stop;
while (true){
stop = true;
for (int i = 0; i < ss.size(); i++)
if ('0' <= ss[i] && ss[i] <= '9'){
ss.erase(i, 1);
stop = false;
}
if (stop)
break;
}
for (int i = 0; i < tt.size(); i++)
if ('a' <= tt[i] && tt[i] <= 'z')
tt[i] -= ('a' - 'A');
else if ('A' <= tt[i] && tt[i] <= 'Z')
tt[i] += ('a' - 'A');
bool cap = ((s.compare(tt) == 0) || (ss.compare(tt) == 0)),
num = (ss.size() < s.size() && ((ss.compare(t) == 0) ||
(ss.compare(tt) == 0)));
if (cap && num)
cout << "Please, check your caps lock and num lock key."
<< endl;
else if (cap && !num)
cout << "Please, check your caps lock key." << endl;
else if (!cap && num)
cout << "Please, check your num lock key." << endl;
else
cout << endl;
}
return 0;
}
54/64
XÓA 7 Tên chương trình: DROP7.???
Xóa 7 là trò chơi một người. Cho lưới ô vuông kích thước 7×7, các cột được đánh số từ 1 đến
7 từ trái sang phải. Ban đầu lưới ô vuông rỗng. Ở mỗi lượt đi sẽ có một viên bi rơi xuống từ
trên cột i. Viên bi được đánh một trong các số từ 1 đến 7. Ở đây chúng ta chỉ xem xét việc xử
lý bảng khi viên bi rơi xuống cột i. Viên
bi sẽ rơi xuống đáy cột i nếu cột này
rỗng hoặc dừng lại khi chạm viên bi trên
cùng trong cột. Mỗi viên bi chiếm đúng 1
ô.
Ví dụ, nếu ta thả viên bi 3 ở cột 1 nó sẽ
dừng lại trên viên bi 6, nếu thả nó ở cột
3, nó sẽ dừng lại ở đáy, còn nếu thả ở cột
7 – dừng trên viên bi 5. Các viên bi trong
một cột tạo thành nhóm cột. Dãy các viên
bi kề nhau trong một hàng hai đầu của
dãy là ô trống hoặc biên của lưới tạo
thành một nhóm hàng. Số lượng bi trong
nhóm gọi kích thước của nhóm. Nếu có
một hoặc một vài viên bi trong nhóm có
số đúng bằng kích thước của nhóm thì
những viên bi đó sẽ bị bốc hơi, biến mất
khỏi bảng, những ô chứa các viên bi này
sẽ trở thành ô trống và các viên bi còn lại (nếu có) trong cột sẽ rơi xuống dưới, lấp vào các ô
trống.
Ví dụ, nếu viên bi 3 được thả vào cột 4, nó sẽ tạo thành một nhóm cột kích thước 3 và nó sẽ
bị bốc hơi. Nếu thả nó vào cột 7, một nhóm cột kích thước 3 cũng sẽ hình thành, các viên bi ở
dòng 1 và 3 sẽ bị bốc hơi và viên bi 4 sẽ rơi xuống đáy. Việc các viên bi bốc hơi làm thay
đổi cấu hình bi trong bảng, hình thành những nhóm hàng, nhóm cột mới và lại có thể làm một
số viên bi nào đó khác bốc hơi!
Cho bảng rỗng ban đầu. Xét n viên bi lần lượt rơi xuống, viên bi thứ i có số là vi rơi xuống
cột ci. Hãy đưa ra trạng thái cuối cùng của bảng dưới ma trận 7×7 ký tự (7 xâu độ dài 7), ký
tự “#” chỉ ô trống, ký tự số chỉ số hiệu của viên bi tại ô đó. Nếu ở một thời điểm nào đó, một
cột đã đầy mà vẫn có bi rơi xuống cột đó thì đưa ra thông báo “Game Over!”.
Dữ liệu: Vào từ file văn bản DROP7.INP gồm nhiều tests, mỗi test cho trên một nhóm dòng,
dòng đầu tiên trong nhóm chứa số nguyên n (1 ≤ n ≤ 1000), dòng thứ i trong n dòng tiếp
theo chứa 2 số nguyên vi và ci. File dữ liệu kết thúc bằng dòng chứa một số 0.
Kết quả: Đưa ra file văn bản DROP7.OUT, kết quả mỗi test được đưa ra dưới dạng 7 dòng,
mỗi dòng một xâu độ dài 7 hoặc đưa ra 1 dòng thông báo “Game Over!”. Giữa kết quả của
2 tests liên tiếp nhau là một dòng trống.
6 5
5
4
7
7
5
5
6
6
6
3
3
Nhóm cột
Nhómhàng
1 2 3 4 5 6 7
55/64
Ví dụ:
DROP7.INP DROP7.OUT 6
2 2
3 2
6 2
4 3
1 1
6 7
11
5 3
5 3
2 3
2 3
2 3
3 3
4 3
5 3
6 3
7 3
1 3
0
#######
#######
#######
#######
#######
#######
#64###6
Game Over!
56/64
C++
#include <iostream>
using namespace std;
int n, a[10][10], top[10];
bool find(){
for (int col = 1; col <= 7; col++)
for (int i = 1; i <= 7; i++)
if (top[col] > 0 && a[col][i] == top[col])
return true;
for (int row = 1; row <= 7; row++){
int i = 1;
while (i <= 7){
while (i <= 7 && a[i][row] == 0)
i++;
int j = i;
while (a[j+1][row])
j++;
for (int k = i; k <= j; k++)
if (j > i && a[k][row] == j-i+1)
return true;
i = j+1;
}
}
return false;
}
void del_column(int col){
if (top[col] == 0)
return;
int num_del = 0;
for (int i = 1; i <= 7; i++)
if (a[col][i] == top[col]){
a[col][i] = 0;
num_del++;
}
top[col] -= num_del;
}
void del_row(int row){
int i = 1;
while (i <= 7){
while (i <= 7 && a[i][row] == 0)
i++;
int j = i;
while (a[j+1][row])
j++;
for (int k = i; k <= j; k++)
if (j > i && a[k][row] == j-i+1){
a[k][row] = 0;
top[k]--;
}
i = j+1;
}
}
57/64
void drop_column(int col){
int num = 0, temp[10];
memset(temp, 0, sizeof(temp));
for (int i = 1; i <= 7; i++)
if (a[col][i]){
temp[++num] = a[col][i];
a[col][i] = 0;
}
for (int i = 1; i <= num; i++)
a[col][i] = temp[i];
}
int main(){
freopen("DROP7.INP", "r", stdin);
freopen("DROP7.OUT", "w", stdout);
while (true){
scanf("%d", &n);
if (n == 0)
break;
memset(a, 0 , sizeof(a));
memset(top, 0, sizeof(top));
int val, col;
bool game_over = false;
while (n--){
scanf("%d %d", &val, &col);
if (top[col] == 7)
game_over = true;
a[col][++top[col]] = val;
while (find()){
for (int col = 1; col <= 7; col++)
del_column(col);
for (int row = 1; row <= 7; row++)
del_row(row);
for (int col = 1; col <= 7; col++)
drop_column(col);
}
}
if (game_over){
printf("Game Over!\n");
continue;
}
for (int row = 7; row >= 1; row--){
for (int col = 1; col <= 7; col++)
if (a[col][row] == 0)
printf("#");
else
printf("%d", a[col][row]);
printf("\n");
}
printf("\n");
}
return 0;
}
58/64
BĂNG GIẤY Tên chương trình: STRIP.PAS
Xét băng giấy có độ dài 2K ô và độ rộng một ô. Các ô dược đánh số từ trái sang phải, bắt đầu từ 1.
Người ta gập đôi băng giấy sao cho các ô đầu tiên nằm ở lớp dưới. Như vậy băng giấy trở thành hai
lớp và độ dài còn một nửa. Người ta cứ gập đôi như vậy cho đến khi nó có 2K lớp.
Yêu cầu: Cho K và N (1 ≤ K ≤ 30, 1 ≤ N ≤ 2 000 000 000), hãy xác định ô thứ N nằm ở lớp thứ mấy
từ dưới lên.
Dữ liệu: Vào từ file văn bản STRIP.INP chứa 2 số nguyên K và N.
Kết quả: Đưa ra file văn bản STRIP.OUT số thứ tự tìm được hoặc giá trị -1 nếu băng giấy
không có ô N.
Ví dụ:
STRIP.INP STRIP.OUT
4 3 9
59/64
C++ #include <fstream>
#include <string>
using namespace std;
int n, k, kk, p = 1, q;
string s;
ifstream fi ("STRIP.INP");
ofstream fo ("STRIP.OUT");
inline void input(){fi >> k >> n;}
inline void initial(){k--; n--; s = "";}
inline void generate_s(){
while (k>=0){
if (((n >> k) & 1) == 0)
s += 'D';
else{
s += 'U';
n = ~n;
}
k--;
}
}
inline void process(){
for (int i = 0; i < s.size(); i++){
if (s[i] == 'U'){
int temp = p;
p = p+(q << 1)+1;
q = temp-1;
}
else
q += p+q;
}
}
inline void output(){
fo << p;
}
int main(){
input();
initial();
generate_s();
process();
output();
return 0;
}
60/64
ĐƢỜNG VIỀN Tên chương trình: CONTOUR.???
Một khu công nghiệp được giới hạn bởi đường gấp khúc khép kín không tự cắt n đỉnh có các
cạnh song song với trục tọa độ (4 ≤ n ≤ 105). Đỉnh thứ i của đường gấp khúc có tọa độ
nguyên (xi, yi), giá trị tuyệt đối của các tọa độ không vượt quá 109.
Người ta làm một con đường chạy dọc theo biên của khu công nghiệp với độ rộng của đường
là d (d nguyên, 0 < d < 1000). Mỗi đoạn thẳng của đường có một cạnh là đường biên của
khu công nghiệp, cạnh kia là đường song song
và nằm ngoài khu công nghiệp. Các cạnh này
tạo thành một hình bao lấy khu công nghiệp.
Hãy xác định tọa độ đỉnh của hình bao.
Dữ liệu: Vào từ file văn bản CONTOUR.INP:
Dòng đầu tiên chứa 2 số nguyên n và d,
Dòng thứ i trong n dòng sau chứa 2 số
nguyên xi và yi.
Tọa độ các đỉnh được cho theo một chiều nào
đó. Dữ liệu đảm bảo tồn tại hình bao.
Kết quả: Đưa ra file văn bản CONTOUR.OUT n dòng, dòng thứ i chứa 2 số nguyên xác định
tọa độ đỉnh thứ i của hình bao. Tọa độ các đỉnh được đưa ra theo chiều ngược kim đồng hồ,
bắt đầu từ đỉnh cao nhất và trái nhất.
Ví dụ:
CONTOUR.INP CONTOUR.OUT 6 1
5 -1
5 5
2 5
2 2
-2 2
-2 -1
1 6
1 3
3 -3
-3 -2
6 -2
6 6
61/64
C++ #include <iostream>
using namespace std;
int n, d, first = 100009, x[100010], y[100010], res_x[100010], res_y[100010];
bool clockwise;
void input(){
cin >> n >> d;
for (int i = 1; i <= n; i++)
cin >> x[i] >> y[i];
x[n+1] = x[1];
y[n+1] = y[1];
x[0] = x[n];
y[0] = y[n];
}
void define_direction(){
int S = 0;
for (int i = 1; i <= n; i++)
S += x[i]*y[i+1] - y[i]*x[i+1];
clockwise = (S < 0);
}
void process(){
y[first] = -INT_MAX;
x[first] = INT_MAX;
for (int i = 1; i <= n; i++){
if (x[i] == x[i-1]){
if (y[i] > y[i-1])
res_y[i] = y[i] + d;
else
res_y[i] = y[i] - d;
if (x[i] < x[i+1])
res_x[i] = x[i] - d;
else
res_x[i] = x[i] + d;
}
else{// x[i] == x[i+1]
if (y[i] > y[i+1])
res_y[i] = y[i] + d;
else
res_y[i] = y[i] - d;
if (x[i] < x[i-1])
res_x[i] = x[i] - d;
else
res_x[i] = x[i] + d;
}
if (y[i] > y[first] || (y[i] == y[first] && x[i] < x[first]))
first = i;
}
}
void output(){
if (!clockwise){
for (int i = first; i <= n; i++)
cout << res_x[i] << " " << res_y[i] << endl;
for (int i = 1; i < first; i++)
cout << res_x[i] << " " << res_y[i] << endl;
}
else{
for (int i = first; i >= 1; i--)
cout << res_x[i] << " " << res_y[i] << endl;
for (int i = n; i > first; i--)
cout << res_x[i] << " " << res_y[i] << endl;
}
}
int main(){
freopen("CONTOUR.INP", "r", stdin);
freopen("CONTOUR.OUT", "w", stdout);
input();
define_direction();
process();
output();
return 0;
}
62/64
MUA KEM Tên chương trình: CREAM.???
Rasmus và các bạn đi nghỉ hè ở Ý. Thời tiết quá nóng bức, các bạn quyết định mua mỗi
người vài cây kem cho đã khát. Có n loại kem mùi khác nhau đánh số từ 1 đến n. Có một số
cặp mùi kỵ nhau làm mất ngon. Rasmus muốn biết có bao nhiêu cách mua 3 cây kem không
có cặp mùi kỵ nhau. Trình tự mua không đóng vai trò quan trọng.
Ví dụ, n = 5 và số cặp mùi kỵ nhau m =3: (1, 2), (3, 4) và (1, 3). Khi đó Rasmus có 3 cách
mua: (1, 4, 5), (2, 3, 5) và (2, 4, 5).
Dữ liệu: Vào từ file văn bản CREAM.INP:
Dòng đầu tiên chứa 2 số nguyên n và m (1 ≤ n ≤ 200, 0 ≤ m ≤ 104),
Mỗi dòng trong m dòng sau chứa 2 số nguyên p và q – cặp mùi kỵ nhau, 1 ≤ p, q ≤ n, p ≠ q,
không có cặp mùi nào bị nêu lặp lại.
Kết quả: Đưa ra file văn bản CREAM.OUT một số nguyên – số cách mua.
Ví dụ:
CREAM.INP CREAM.OUT 5 3
1 2
3 4
1 3
3
C++ #include <iostream>
using namespace std;
int n, m, u, v, res = 0;
bool contrast[201][201];
int main(){
freopen("CREAM.INP", "r", stdin);
freopen("CREAM.OUT", "w", stdout);
scanf("%d %d", &n, &m);
while (m--){
scanf("%d %d", &u, &v);
contrast[u][v] = contrast[v][u] = true;
}
for (int i = 1; i <= n-2; i++)
for (int j = i+1; j <= n-1; j++)
for (int k = j+1; k <= n; k++)
res += !(contrast[i][j] || contrast[i][k] ||
contrast[j][k]);
printf("%d", res);
return 0;
}
63/64
HÌNH KHỐI Tên chương trình: CUBES.???
Từ một hình lập phương ban đầu (gọi là hình khối bậc 0) người ta dán vào các mặt của nó
hình lập phương cùng kích thước và được hình khối bậc 1. Từ hình khối bậc 1 người ta dán
các hình lập phương vào các mặt trống của nó và được hình khối bậc 2 (Xem hình vẽ). Từ
hình khối bậc i người ta tạo ra hình khối bậc i+1 bằng cách dán các hình lập phương vào các
mặt trống của nó.
Yêu cầu: Cho số nguyên n ( 0 ≤ n ≤ 100 000). Hãy xác định số khối lập phương tạo ra hình
khối bậc n.
C++ #include <iostream>
#include <fstream>
using namespace std;
int n, m, u, v, res = 0;
int c[201][201];
int main()
{ ifstream fi ("CREAM.INP");
ofstream fo ("CREAM.OUT");
fi>>n>>m;
{int c[n+1][n+1],t;
memset(c,0,sizeof(c));
for (int i=1;i<=m;i++)
{fi>>u>>v;
c[u][v]=c[v][u]=1;
}
for (int i = 1; i <= n-2; i++)
for (int j = i+1; j <= n-1; j++)
for (int k = j+1; k <= n; k++)
{t=c[i][j]+c[i][k]+c[j][k];
if (t==0) res++;
}
}
fo<<res;
fo.close(); fi.close();
}
Hình khối
bậc 0
Hình khối
bậc 1
Hình khối
bậc 2
64/64
Dữ liệu: Vào từ file văn bản CUBES.INP, gồm nhiều Tests, mỗi test cho bởi số nguyên n,
ghi trên một dòng.
Kết quả: Đưa ra file văn bản CUBES.OUT, kết quả mỗi test là một số nguyên, ghi trên một
dòng.
Ví dụ:
CUBES.INP CUBES.OUT
1
2
7
25
C++ #include <fstream>
using namespace std;
ifstream fi ("CUBES.INP");
ofstream fo ("CUBES.OUT");
int n,ho,hn,co,cn,r;
int main()
{while (!fi.eof())
{hn=1;cn=1;
fi>>n;
for (int i = 1; i<n;i++)
{ho=hn;co=cn;
cn=co+i*4;
hn=ho+cn;
}
r=cn+2*ho;
fo<<r<<endl;
}
fo.close(); fi.close();
}