Strict Standards: Non-static method HTML_content::TOC() should not be called statically in /home/vmapps4u/public_html/vnoi_v0/components/com_content/content.html.php on line 526
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:
- Sắp xếp lại hai dãy số theo thứ tự tăng dần.
- 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:
- Sắp xếp lại dãy số theo thứ tự tăng dần
- 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;
- 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.
|