You are on page 1of 11

Đề thi HSG QG 2008

Trò chơi với dãy số


Mã bài: SEQGAME

Submission link: <a


href="http://vn.spoj.pl/IOITRAIN/problems/NKSGAME">vn.spoj.pl/IOITRAIN/
problems/NKSGAME</a>

Hai bạn học sinh trong lúc nhàn rỗi nghĩ ra trò chơi sau đây. Mỗi bạn chọn trước một dãy số gồm n số
nguyên. Giả sử dãy số mà bạn thứ nhất chọn là:

b1, b2, ..., bn


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

c1, c2, ..., cn


Mỗi lượt chơi mỗi bạn đưa ra một số hạng trong dãy số của mình. Nếu bạn thứ nhất đưa ra số hạng b i (1 ≤ i
≤ n), còn bạn thứ hai đưa ra số hạng cj (1 ≤ j ≤ n) thì giá của lượt chơi đó sẽ là |bi+cj|.
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
 Dòng đầu tiên chứa số nguyên dương n (n ≤ 105)
 Dòng thứ hai chứa dãy số nguyên b1, b2, ..., bn (|bi| ≤ 109, i=1, 2, ..., n)
 Dòng thứ hai chứa dãy số nguyên c1, c2, ..., cn (|ci| ≤ 109, i=1, 2, ..., n)
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ả
Ghi ra giá nhỏ nhất tìm được.

Ràng buộc
 60% số tests ứng với 60% số điểm của bài có 1 ≤ n ≤ 1000.
Ví dụ

<b>Dữ liệu:</b>

2
1 -2
2 3
<b>Kết qủa</b>
0

LÒ CÒ
Mã bài: JUMP

Submission link: <a


href="http://vn.spoj.pl/IOITRAIN/problems/NKJUMP">vn.spoj.pl/IOITRAIN/problems/
NKJUMP</a>
Nhảy lò cò là trò chơi dân gian của Việt Nam. Người trên hành tinh X cũng rất thích trò chơi này và họ đã
cải biên trò chơi này như sau: Trên mặt phẳng vẽ n vòng tròn được đánh số từ 1 đến n. Tại vòng tròn i
người ta điền số nguyên dương ai. Hai số trên hai vòng tròn tùy ý không nhất thiết phải khác nhau. Tiếp đến
người ta vẽ các mũi tên, mỗi mũi tên hướng từ một vòng tròn đến một vòng tròn khác. Quy tắc vẽ mũi tên
là: Nếu có ba số ai, aj, ak thỏa mãn ak = ai + aj thì vẽ mũi tên hướng từ vòng tròn i đến vòng tròn k và mũi tên
hướng từ vòng tròn j đến vòng tròn k. Người chơi chỉ được di chuyển từ một vòng tròn đến một vòng tròn
khác nếu có mũi tên xuất phát từ một trong số các vòng tròn, di chyển theo cách mũi tên đã vẽ để đi đến
các vòng tròn khác. Người thắng cuộc sẽ là người tìm được cách di chuyển qua nhiều vòng tròn nhất.
Ví dụ: Với 5 vòng tròn và các số trong vòng tròn là 1, 2, 8, 3, 5, trò chơi được trình bày trong hình dưới đây:

Khi đó có thể di chuyển được nhiều nhất qua 4 vòng tròn (tương ứng với đường di chuyển được tô đậm
trên hình vẽ).

Yêu cầu
Hãy xác định xem trong trò chơi mô tả ở trên, nhiều nhất có thể di chuyển được qua bao nhiêu vòng tròn.

Dữ liệu
 Dòng đầu chứa số nguyên n (3 ≤ n ≤ 1000);
 Dòng thứ hai chứa dãy số nguyên dương a1, a2, ..., an (ai ≤ 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ả
Ghi ra số lượng vòng tròn trên đường di chuyển tìm được.

Ràng buộc
 60% số tests ứng với 60% số điểm của bài có 3 ≤ n ≤ 100.
Ví dụ

<b>Dữ liệu:</b>

5
1 2 8 3 5
<b>Kết qủa</b>
4

3. QUÀ TẾT
Mã bài: GIFTS
Submission link: <a
href="http://vn.spoj.pl/IOITRAIN/problems/NKGIFTS">vn.spoj.pl/IOITRAIN/
problems/NKGIFTS</a>
Chuẩn bị đón năm mới. Công ty bánh kẹo Hương Dứa đã làm một tấm sôcôla cực lớn với mục đích ghi tên
mình vào sách kỷ lục Ghi-nét đồng thời quảng bá thương hiệu trước công chúng. Tấm sôcôla có hình
vuông kích thước 2kx2k, tạo thành lưới ô vuông 2k hàng và 2k cột. Các hàng được đánh số từ 0 đến 2k-1 từ
trên xuống dưới, các cột được đánh số từ 0 đến 2k-1 từ trái sang phải. Ô nằm ở hàng i và cột j được gọi là ô
(i, j). Sau buổi trưng bày giới thiệu sản phẩm, tấm sôcôla được cắt nhỏ, chia cho mọi người, mỗi người
được một ô của chiếc bánh kỷ lục. Bộ phận tiếp thị đã ấn vào hai ô khác nhau (p, q) và (u, v) mỗi ô một
đồng xu. Vị khách nào may mắn nhận được ô sôcôla có đồng xu sẽ được tặng rất nhiều sản phẩm độc đáo
của công ty.

Vì chiếc bánh rất lớn nên công ty đã thiết kế một máy cắt bánh. Máy thực hiện dãy các thao tác cắt, bắt đầu
từ chồng bánh chỉ gồm 1 tấm sôcôla ban đầu, mỗi thao tác gồm hai bước sau:

 Bước 1: Cắt ngang song song với cạnh chồng bánh chia chồng sôcôla thành hai phần bằng nhau,
úp chồng bánh bên dưới lên chồng bánh bên trên sao cho mép dưới đè lên mép trên.
 Bước 2: Cắt dọc song song với cạnh chồng bánh chia chồng sôcôla thành hai phần bằng nhau, úp
chồng bánh bên trái lên chồng bánh bên phải sao cho mép trái đè lên mép phải.
Như vậy sau mỗi lần thực hiện thao tác cắt, chiều dài và chiều rộng của các tấm sôcôla giảm đi một nửa.
Sau k lần thực hiện thao tác cắt, các ô của tấm sôcôla sẽ được xếp thành một cột. Khách nhận bánh xếp
hàng một và được đánh số từ 1 trở đi, người thứ m sẽ nhận được miếng sôcôla thứ m từ trên xuống dưới.
(1 ≤ m ≤ 2k x 2k).
Ví dụ, với k=1 và đồng xu được ấn vào các ô (0,0), (1,1), việc thực hiện các thao tác cắt sẽ được trình bày
trên hình vẽ minh họa ở trên. Trong ví dụ này, vị khách thứ nhất và thứ ba sẽ là những người nhận được
tặng phẩm của công ty.

Yêu cầu
Cho biết các số nguyên k, p, q, u, v. Hãy xác định số thứ tự của hai vị khách may mắn nhận được quà.

Dữ liệu
Gồm một dòng chứa 5 số nguyên k, p, q, u, v, các số cách nhau bởi dấu cách.

Kết quả
Một dòng chứa hai số nguyên là số thứ tự các vị khách may mắn. Hai số phải cách nhau đúng một dấu
cách.

Ràng buộc
 1 ≤ k ≤ 40, 0 ≤ p, q, u, v ≤ 2k - 1.
 60% số tests ứng với 60% số điểm của bài có 1 ≤ k ≤ 5.
Ví dụ
<strong>Dữ liệu:</strong>

1 0 0 1 1

<strong>Kết qủa</strong>
1 3

Lời giải đề thi quốc gia 2008


Bài 1: Trò chơi với dãy số
Phát biểu lại:

Cho hai dãy số nguyên có n phần tử


b1, b2,..., bn và c1, c2,..., cn
Yêu cầu:
Chọn ra hai số nguyên từ hai dãy bi và cj sao cho |bi + cj| có giá trị nhỏ nhất.
Giới hạn:
n <= 105
Các số nguyên trong hai dãy đã cho có giá trị tuyệt đối không quá 109.
Thuật toán:
1. Sắp xếp lại hai dãy số theo thứ tự tăng dần.
2. Dùng hai biến chạy i và j. Biến i xuất phát từ đầu dãy b, biến j xuất phát từ cuối dãy c. Bước vào
vòng lặp, ta xét giá trị bi + cj, chia ra hai trường hợp:
bi + cj >= 0: cần giảm j (vì nếu tăng giá trị i sẽ không thu được kết quả nhỏ hơn)
bi + cj < 0: cần tăng i (vì nếu giảm giá trị j sẽ không thu được kết quả nhỏ hơn)
Mã giả của bước 2 như sau:
i <- 1;
j <- n;
r <- oo;
while (i<=n AND j>=1) do
r=min(r, |bi + cj|)
if (bi + cj >= 0)
j <- j-1;
else
i<- i+1;
return r;
//r là kết quả cần tìm.
Thời gian thực hiện:
Bước 1 (sắp xếp) có thời gian thực hiện O(nlogn). Bước 2 có thời gian thực hiện O(n) do hai biến chạy i và j
dừng tại mỗi phần tử nhiều nhất một lần. Do đó toàn bộ thuật toán có thời gian thực hiện O(nlogn).
Nhận xét: sắp xếp lại dãy số và sử dụng biến chạy là kỹ thuật thường gặp khi xử lý dãy số. Thuật toán
O(nlogn) trên đây đủ thời gian để đạt được trọn điểm qua các test. Dùng thuật toán O(n2) đơn giản: xét tất
cả các cặp số, cũng có thể đạt được 60 điểm (theo đề bài đã nêu).
Bài 2: Lò cò
Ta đưa bài toán về dạng đồ thị: xem mỗi vòng tròn là một đỉnh của đồ thị. Đậy là một đồ thị có hướng và
quy tắc xác định tập cạnh như sau:
Nếu có 3 đỉnh i, j, k sao cho ai + aj = ak thì có cạnh (i, k) và cạnh (j, k).
Nhận xét: đồ thị này không có chu trình, hay ta nói đồ thị có thứ tự topo. Bài toán trở thành một bài toán cơ
bản: tìm đường đi dài nhất trên đồ thị có hướng không có chu trình.
Một thứ tự topo hiển nhiên cho tập đỉnh chính là thứ tự tăng dần của các số. Xác định tập cạnh cho đồ thị
cũng là một vấn đề quan trọng. Vì giới hạn số đỉnh là 1000 nên việc dùng vòng lặp O(n3) xét tất cả các bộ
ba (i, j, k) để tìm tập cạnh của đồ thị là không khả thi. Ta sử dụng nhận xét đơn giản sau: nếu ai + aj > ak thì
ai + aj’ > ak với j’ > j. Từ đó có thuật toán sau:
1. Sắp xếp lại dãy số theo thứ tự tăng dần
2. Xác định tập cạnh của đồ thị. Mã giả của bước 2:
for i <- 1 to n-2 do
t <- i+2;
for j <- i+1 to n-1 do
for k <- t to n do
if ai + aj = ak
xác định được cạnh (i, k) và (j, k)
else if ai + aj < ak
break;
t=k;
3. Dùng phương pháp qhđ để tìm đường đi dài nhất trên đồ thị. Mã giả của bước 3:
r=-1;
for i <- 1 to n do
f[i]<-1;
for j<-1 to i-1 do
if ( (j, i) là cạnh) AND (f[j]+1>f[i])
f[i]<-f[j]+1;
if (f[i]>r) r<-f[i];
//r là kết quả
Thời gian thực hiện:
Bước 1 có độ phức tạp O(nlogn). Trong bước 2, mặc dù có 3 vòng lặp, nhưng giá trị k sẽ luôn chỉ chạy qua
dãy số đúng một lần (do mỗi lần được bắt đầu từ biến độc lập t), do đó bước 2 có độ phức tạp O(n2). Bước
3 cũng có độ phức tạp O(n2). Vậy tổng thời gian thực hiện của thuật toán là O(n2).
Nhận xét: bài toán này kết hợp giữa một bài tập cơ bản về đồ thị (tìm đường đi dài nhất trên đồ thị có
hướng không có chu trình) và việc xử lý trên dãy số. Có thể đạt được giới hạn 60% số test đề bài nêu nếu
sử dụng vòng lặp O(n3) để xác định tập cạnh.
Bài 3: Quà tết
Phát biểu dưới dạng bài toán gấp giấy. Nhiệm vụ của ta là từ tọa độ (i, j) của ô vuông, tìm giá trị z là độ cao
của ô vuông đó sau khi hoàn thành việc gấp giấy.
Ta sử dụng phương pháp biến đổi từng bước. Ban đầu ta có: i = tọa độ i ban đầu, j = tọa độ j ban đầu, z =
0. Cần xác định sự thay đổi của các giá trị i, j, z sau mỗi lần gấp, các giá trị i, j, z. Sau 2k lần gấp giá trị z thu
được là kết quả cần tìm.
Để xác định quy tắc biến đổi của i, j, z, có thể dùng nhiều phương pháp, một trong số đó là dùng giấy bút
mô phỏng một bước biến đổi.
Mã giả của thuật toán như sau:
z <- 1;
t <- 0;
while (k>0) do
if i>=2k-1
i<-2k – i – 1;
j<-t-z+1;
else
z<-z+2t;
t<-t+1;
if (j>=2k-1)
j<-j-2k-1;
z<-z+2t;
else
j<-2k-1-j-1;
z<-2t-z+1;
k<-k-1;
t<-t+1;
Nhận xét: Do kết quả có thể lên đến 280 nên cần dùng kiểu số nguyên lớn để tính toán kết quả.
Nhận xét chung
Đề thi quốc gia năm 2008 có mức độ không khó nhưng đòi hỏi phải nắm vững những kiến thức cơ bản và
làm bài cẩn thận.
Chương trình giải
Những chương trình giải dưới đây đều thể hiện lời giải đúng mặc dù có thể sử dụng thuật toán khác với đã
trình bày, hoặc dùng thuật toán tương tự nhưng khác về thứ tự các bước.

Bài 1:
(C++ / Tác giả: Ngô Minh Đức)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <set>
#include <string>
using namespace std;
#define FIN "SEQGAME.INP"
#define FOUT "SEQGAME.OUT"
#define NMAX 110000
int n;
int b[NMAX], c[NMAX];
int main()
{
freopen(FIN,"r",stdin);
freopen(FOUT,"w",stdout);
scanf("%d", &n);
for (int i=0; i<n; i++)
scanf("%d", &b[i]);
for (int i=0; i<n; i++)
scanf("%d", &c[i]);
sort(b,b+n);
sort(c,c+n);
int i=0, j=n-1;
int r=2100000000;
while (i<n && j>=0)
{
r=min(r, abs(b[i]+c[j]));
if (b[i]+c[j]>=0)
j--;
else
i++;
}
printf("%d",r);
return 0;
}
(Pascal / Tác giả: Phạm Quang Vũ)
program SEQGAME;
const
InputFile = 'SEQGAME.INP';
OutputFile = 'SEQGAME.OUT';
max = 100000;
type
TArray = array[1..max] of Integer;
var
b, c: TArray;
n: Integer;
DiffMin: Integer;
procedure Enter;
var
f: TextFile;
i, temp: Integer;
begin
AssignFile(f, InputFile); Reset(f);
try
ReadLn(f, n);
for i := 1 to n do Read(f, b[i]);
ReadLn(f);
for i := 1 to n do
begin
Read(f, temp);
c[i] := -temp;
end;
finally
CloseFile(f);
end;
end;
procedure QuickSort(var k: TArray; L, H: Integer);
var
i, j: Integer;
temp, pivot: Integer;
begin
if L >= H then Exit;
pivot := k[L + Random(H - L + 1)];
i := L; j := H;
repeat
while k[i] < pivot do Inc(i);
while k[j] > pivot do Dec(j);
if i <= j then
begin
if i < j then
begin
temp := k[i]; k[i] := k[j]; k[j] := temp;
end;
Inc(i); Dec(j);
end;
until i > j;
QuickSort(k, L, j);
QuickSort(k, i, H);
end;
procedure Update(x, y: Integer); inline;
begin
if Abs(x - y) < DiffMin then DiffMin := Abs(x - y);
end;
procedure Solve;
var
v, i, j: Integer;
begin
DiffMin := MaxInt;
j := 1;
for i := 1 to n do
begin
v := b[i];
while (j <= n) and (v > c[j]) do Inc(j);
if j <= n then Update(v, c[j]);
if j > 1 then Update(v, c[j - 1]);
end;
end;
procedure PrintResult;
var
f: TextFile;
begin
AssignFile(f, OutputFile); Rewrite(f);
try
WriteLn(f, DiffMin);
finally
CloseFile(f);
end;
end;
begin
Enter;
QuickSort(b, 1, n);
QuickSort(c, 1, n);
Solve;
PrintResult;
end.
4
1 2 999999999 1000000000
-5 -6 -999999997 -999999998
Bài 2:
(Pascal / Tác giả: Ngô Minh Đức)
const
finp='JUMP.INP';
fout='JUMP.OUT';
nmax=1200;
var
i, j, k, t, n, last, kq: longint;
g: array[1..nmax, 1..nmax] of boolean;
a: array[1..nmax] of longint;
f: array[1..nmax] of longint;
begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);
read(n);
for i:=1 to n do
read(a[i]);
for i:=1 to n-1 do
for j:=i+1 to n do
if (a[i]>a[j]) then
begin
t:=a[i];
a[i]:=a[j];
a[j]:=t;
end;
fillchar(g, sizeof(g), false);
for i:=1 to n-2 do
begin
last:=i+2;
for j:=i+1 to n-1 do
for k:=last to n do
begin
if a[i]+a[j]=a[k] then
begin
g[i, k]:=true;
g[j, k]:=true;
end else if a[k]>a[i]+a[j] then
break;
last:=k;
end;
end;
kq:=-1;
for i:=1 to n do
begin
f[i]:=1;
for j:=1 to i-1 do
if (g[j, i]) and (f[j]+1>f[i]) then
f[i]:=f[j]+1;
if f[i]>kq then
kq:=f[i];
end;
writeln(kq);
close(input);
close(output);
end.
Bài 3:
(Pascal / Tác giả: Lê Văn Hồng Chân)
const
fin='GIFTS.INP';
fon='GIFTS.OUT';
maxn=40;
rad=1000000000000000;
type
bigint=record l,h:qword end;
var
fi,fo:text;
n,m,x1,x2,y1,y2:qword;
i,k:longint;
d1,d2,h,s:bigint;
operator - (a,b:bigint) c:bigint;
begin
c.h:=a.h-b.h;
if a.l<b.l then begin c.h:=c.h-1; a.l:=a.l+rad end;
c.l:=a.l-b.l;
end;
operator + (a,b:bigint) c:bigint;
begin
c.l:=a.l+b.l;
c.h:=(c.l div rad)+a.h+b.h;
c.l:=c.l mod rad;
end;
begin
assign(fi,fin); reset(fi);
assign(fo,fon); rewrite(fo);
read(fi,k,x1,y1,x2,y2);
n:=1; d1.l:=1; d2.l:=1;
for i:=1 to k do n:=n shl 1;
n:=n-1; s.l:=1; h.l:=1;
repeat
m:=n shr 1;
if x1>m then begin x1:=n-x1; d1:=h-d1+s end else d1:=d1+h;
if x2>m then begin x2:=n-x2; d2:=h-d2+s end else d2:=d2+h;
h:=h+h; m:=m+1;
if y1<m then begin y1:=n-y1; d1:=h-d1+s end else d1:=d1+h;
if y2<m then begin y2:=n-y2; d2:=h-d2+s end else d2:=d2+h;
y1:=y1-m; y2:=y2-m;
n:=m-1; h:=h+h;
until n=0;
if d1.h>0 then write(fo,d1.h); write(fo,d1.l,' ');
if d2.h>0 then write(fo,d2.h); writeln(fo,d2.l);
close(fi); close(fo);
end.

You might also like