Skip to content
Narrow screen resolution Wide screen resolution Auto adjust screen size Increase font size Decrease font size Default font size default color grey color
         
 | 
VNOI - Olympic tin học Việt Nam

Điểm tin VOJ

Số thành viên:6040
Số bài tập:1001
Số bài nộp:722923
Bài nộp hôm nay:0

Top 10 thành viên xuất sắc

HạngThành viênĐiểm
1mr_invincible587.9
2white_cobra418.6
3hieult403.4
4phaleq384.0
5vodanh9x368.2
6con_nha_ngheo352.0
7flash_mt350.2
8darksabers349.8
9yenthanh132345.3
10rockman9x_94343.1
Diễn đàn arrow Thư viện arrow Đề thi arrow VOI (thi quốc gia) arrow 2010 arrow Lời giải đề thi quốc gia 2008
Lời giải đề thi quốc gia 2008 In E-mail
(21 votes)
Người viết: Ngô Minh Đức   
26/04/2008

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;

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

 
Tiếp >