Tải bản đầy đủ (.doc) (37 trang)

MỘT CÁCH TIẾP CẬN MỚI VỀ QUY HOẠCH ĐỘNG

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (703.43 KB, 37 trang )

CHUYÊN ĐỀ
MỘT CÁCH TIẾP CẬN MỚI VỀ QUY HOẠCH ĐỘNG
Trong chiến lược chia để trị, người ta chia bài toán cần giải thành các bài toán con. Các bài toán
con lại tiếp tục được chia thành các bài toán con nhỏ hơn, cứ tiếp tục chia cho đến khi ta nhận được các
bài toán con có thể giải được dễ dàng. Tuy nhiên, trong quá trình phân chia như vậy, có thể ta sẽ gặp lại
rất nhiều lần cùng một bài toán con. Tư tưởng cơ bản của phương pháp quy hoạch động là sử dụng bảng
để lưu trữ lời giải của các bài toán con đã được giải. Khi giải bài toán con cần đến nghiệm của các bài toán
con cỡ nhỏ hơn, ta chỉ cần lấy lời giải trong bảng mà không cần phải giải lại. Chính vì thế mà các thuật
toán được thiết kế bằng phương pháp quy hoạch động sẽ rất hiệu quả.
Để giải quyết một bài toán bằng phương pháp quy hoạch động, chúng ta cần tiến hành những công
việc sau:
1) Tìm các tham số mô tả bài toán;
2) Tìm nghiệm của các bài toán con nhỏ nhất;
3) Tìm công thức (hoặc quy tắc) xây dựng nghiệm của bài toán con thông qua nghiệm của bài
toán con cỡ nhỏ hơn;
4) Tạo bảng (dựa trên các tham số mô tả bài toán) để lưu trữ nghiệm của các bài toán con. Tính
nghiệm của các bài toán con theo công thức đã tìm và lưu trữ vào bảng;
5) Từ các bài toán con đã giải để tìm nghiệm của bài toán.
1. Lớp các bài toán giải được bằng phương pháp quy hoạch động


Bài toán tối ưu
o Max / Min
• Bài toán đếm
o Đếm số cấu hình
o Thứ tự từ điển
• Bài toán lập bảng phương án
o Xây dựng cấu hình
o Trò chơi
2. Các cách cài đặt



Bottom-up (từ dưới lên)
o Khi đã xác định được thứ tự các bài toán con cần giải
o Cài đặt bằng vòng lặp
o Có thể giải thừa các bài toán con không cần thiết
• Top-down (từ trên xuống)
o Không cần thiết xác định được thứ tự các bài toán con cần giải
o Cài đặt bằng đệ quy có nhớ  có thể đặt cận
o Chỉ giải các bài toán con cần thiết
3. Độ phức tạp của thuật toán thiết kế theo phương pháp quy hoạch động
Giả sử bài toán được mô tả bằng các tham số

và cần tính hàm mục tiêu

.

Khi đó, số bài toán con (hay còn gọi là số trạng thái) cần tính là
Gọi
là độ phức tạp tính nghiệm của bài toán con thông qua nghiệm của bài toán con cỡ nhỏ hơn
(còn gọi là chi phí chuyển trạng thái).
Khi đó độ phức tạp của thuật toán bằng: Số trạng thái
1

chi phí chuyển trạng thái (

).


4. Những vấn đề cần chú ý khi giải bài toán bằng phương pháp quy hoạch động
4.1. Tìm các tham số mô tả bài toán

Việc tìm các tham số mô tả bài toán được dựa trên các đặc điểm, tính chất của bài toán (công việc
này còn được gọi là đoán nhận trạng thái). Cụ thể, cần xác định có những tham số nào, ý nghĩa mỗi tham
số, miền giá trị của từng tham số. Đây là công việc quan trọng mang ý nghĩa quyết định đến việc giải
quyết bài toán.
4.2. Tối ưu thuật toán
Qua cách tính độ phức tạp thuật toán theo phương pháp quy hoạch động (độ phức tạp bằng số
trạng thái nhân với chi phí chuyển trạng thái) ta nhận thấy, để giảm độ phức tạp thuật toán có thể giảm
thiểu số trạng thái hoặc giảm chi phí chuyển trạng thái.
5. Một số ví dụ minh hoạ
5.1. Bài toán cắt hình chữ nhật
Có một hình chữ nhật MxN ô, mỗi lần ta được phép cắt một hình chữ nhật thành hai hình chữ nhật
con theo chiều ngang hoặc chiều dọc và lại tiếp tục cắt các hình chữ nhật con cho đến khi được hình
vuông thi dừng.
Yêu cầu: Tìm cách cắt hình chữ nhật MxN thành ít hình vuông nhất.
Input
- Gồm một dòng chứa hai số M, N (1≤M,N≤500)
Output
- Gồm một dòng là kết quả số lượng hình vuông ít nhất.
hcn.inp
10 2
5 6

hcn.out
5
5

Phân tích
-

Dễ nhận thấy bài toán có thể mô tả bằng hai tham số

chiều thứ hai của hình chữ nhật,
thước
giá trị

trong đó

là số hình vuông ít nhất được cắt từ hình chữ nhật kích

.

Các bài toán con nhỏ nhất (cơ bản) có

-

Công thức tính:

-

Như vậy, có

, khi đó

bài toán con và chi phí chuyển mất

toán là:

.

Cài đặt bằng vòng lặp
const MAX

=500;
fi
='hcn.inp';
fo
='hcn.out';
f
m,n



. Khi đó, dùng bảng F[1..500,1..500] để lưu lại các

-

var

là chiều thứ nhất,

:array[1..MAX,1..MAX]of longint;
:longint;
2

.

, do đó độ phức tạp của thuật


procedure tinh(m,n:longint);
var a,b,c,res:longint;
begin

for a:=1 to m do
for b:=1 to n do
begin
if a=b then begin
f[a,b]:=1;
continue;
end;
res:=a*b;
for c:=1 to a-1 do
if res>(f[c,b]+f[a-c,b]) then
res:=(f[c,b]+f[a-c,b]);
for c:=1 to b-1 do
if res>(f[a,c]+f[a,b-c]) then
res:=(f[a,c]+f[a,b-c]);
f[a,b]:=res;
end;
end;
BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(m,n);
tinh(m,n);
writeln(f[m,n]);
close(input);
close(output);
END.
Cài đặt bằng đệ quy có nhớ
const MAX
=500;
fi

='hcn.inp';
fo
='hcn.out';
var

f
m,n

:array[1..MAX,1..MAX]of longint;
:longint;

function tinh(a,b:longint):longint;
var c,res:longint;
begin
if f[a,b]<>-1 then exit(f[a,b]);
3


if a=b then begin
f[a,b]:=1;
exit(f[a,b]);
end;
res:=a*b;
for c:=1 to a-1 do
if res>(tinh(c,b)+tinh(a-c,b)) then
res:=(f[c,b]+f[a-c,b]);
for c:=1 to b-1 do
if res>(tinh(a,c)+tinh(a,b-c)) then
res:=(f[a,c]+f[a,b-c]);
f[a,b]:=res;

exit(f[a,b]);
end;
BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
readln(m,n);
fillchar(f,sizeof(f),255);
writeln(tinh(m,n));
close(input);
close(output);
END.
5.2. Bốc sỏi


đống sỏi xếp thành một vòng tròn, đống thứ có
viên sỏi. Ta có 2 cách bốc sỏi như sau:
- Chọn hai đống liền nhau có số lượng sỏi đều là lẻ. Sau đó bốc từ hai đống đó, mỗi đống một viên
sỏi;
- Chọn hai đống liền nhau vẫn còn sỏi. Sau đó lần lượt bốc từ hai đống đó, để sau khi bốc số sỏi còn
lại của mỗi đống bằng số sỏi ban đầu của đống đó div 2.
Yêu cầu: Cho số lượng sỏi ban đầu của mỗi đống
, hãy tính số cách khác nhau để bốc được
hết tất cả các sỏi.
Input
-

Dòng đầu là số đống sỏi

;


- Dòng thứ hai gồm
số mô tả số lượng sỏi của
đống.
Output
- Gồm một dòng là số lượng cách khác nhau mod 111539786.
BOCSOI.INP
BOCSOI.OUT
3
2
1 2 3
3
0
4


1 1 1
Subtask 1:



Subtask 2:
Subtask 3: Dự đoán giới hạn của
Phân tích

và giải quyết.

Với Subtask 1, ta có thể sử dụng 3 tham số
để mô tả bài toán và
là số cách khác nhau
để bốc được hết tất cả các đống sỏi. Dùng mảng F[0..30,0..30,0..30] để lưu kết quả của các bài toán con

.
Để thuận tiện khi viết chương trình và giải được Subtask 2, ta dùng

để mô

tả bài toán (trường hợp
thì
bằng 0) và dùng mảng F có 5 chiều (mỗi chiều từ 0
đến 30) để lưu trữ. Đoạn lệnh để tính cụ thể như sau:
function qhd(var x:prob):longint;
var i,j :longint;
z
:prob;
tmp :longint;
begin
if f[x[0],x[1],x[2],x[3],x[4]]<>-1 then
exit(f[x[0],x[1],x[2],x[3],x[4]]);
tmp:=0;
for i:=0 to n-1 do begin
j:=(i+1) mod n;
z:=x;
if (z[i]>0) and (z[j]>0) then begin
if odd(z[i]) and odd(z[j]) then begin
dec(z[i]); dec(z[j]);
tmp:=(tmp + qhd(z)) mod NMOD;
end;
z[i]:=x[i] div 2; z[j]:=x[j] div 2;
tmp:=(tmp + qhd(z)) mod NMOD;
end;
end;

f[x[0],x[1],x[2],x[3],x[4]]:=tmp;
exit(tmp);
end;
Số bài toán con theo thuật toán trên là
tạp theo thuật toán trên là

, chi phí chuyển trạng thái

. Như vậy, chương trình trên có thể đáp ứng cho

Subtask 2. Với Subtask 3, ta có nhận xét sau: xét một đống có số sỏi bằng
- nếu
- nếu

. Do dó, độ phức

, ta có thể bốc 1 viên để đưa đống sỏi về còn
, chỉ có cách bốc để đưa đống sỏi về còn
5

viên.

, có 2 trường hợp xảy ra:

, sau đó sẽ về còn

viên;


Nhận thấy các khả năng


không xảy ra. Như vậy, bản chất độ phức tạp thuật

toán trên là
và có thể giải với
. Nhưng
vấn đề khó khăn là không thể khai báo được mảng 5 chiều, mỗi chiều kích thước 30000 để lưu trữ được.
Để giải quyết vấn đề này, với từng đống sỏi ta xét các giá trị có thể xảy ra rồi đánh thứ tự các giá trị này từ
nhỏ đển lớn, việc lưu trữ sẽ theo số thứ tự giá trị. Chương trình hoàn chỉnh dưới đây.
const

MAX
LIMIT
NMOD
fi
fo

=5;
=28;
=111539786;
='bocsoi.inp.';
='bocsoi.out';

type

prob

=array[0..MAX]of longint;

var


s
:prob;
idx
:array[0..30000]of longint;
f
:array[0..LIMIT,0..LIMIT,0..LIMIT,0..LIMIT,0..LIMIT] of longint;
n
:longint;
ft,gt
:text;
procedure chuanbi;
var i, maxIdx :longint;
function getIndex(i:longint):longint;
begin
if idx[i]<>-1 then exit(idx[i]);
if odd(i) then idx[i]:=getIndex(i-1)+1
else idx[i]:=getIndex(i div 2)+1;
exit(idx[i]);
end;
begin
fillchar(idx,sizeof(idx),255);
idx[0]:=0;
maxIdx:=0;
for i:=30000 downto 0 do begin
idx[i]:=getIndex(i);
if idx[i]>maxIdx then maxIdx:=idx[i];
end;
//
writeln(maxIdx);

end;
function
var i,j
y,z
tmp
begin

qhd(var x:prob):longint;
:longint;
:prob;
:longint;
6


for i:=0 to 4 do y[i]:=idx[x[i]];
if f[y[0],y[1],y[2],y[3],y[4]]<>-1 then
exit(f[y[0],y[1],y[2],y[3],y[4]]);
tmp:=0;
for i:=0 to n-1 do begin
j:=(i+1) mod n;
z:=x;
if (z[i]>0) and (z[j]>0) then begin
if odd(z[i]) and odd(z[j]) then begin
dec(z[i]); dec(z[j]);
tmp:=(tmp + qhd(z)) mod NMOD;
end;
z[i]:=x[i] div 2; z[j]:=x[j] div 2;
tmp:=(tmp + qhd(z)) mod NMOD;
end;
end;

f[y[0],y[1],y[2],y[3],y[4]]:=tmp;
exit(tmp);
end;
procedure xuly;
var i :longint;
begin
fillchar(f,sizeof(f),255);
f[0,0,0,0,0]:=1;
fillchar(s,sizeof(s),0);
readln(ft,n);
for i:=0 to n-1 do
read(ft,s[i]);
writeln(gt,qhd(s));
end;
BEGIN
chuanbi;
assign(ft,fi); reset(ft);
assign(gt,fo); rewrite(gt);
xuly;
close(ft);
close(gt);
END.
7


5.3. Least-Squares Segmentation (LSS)
Ta định nghĩa trọng số của đoạn số từ số ở vị trí thứ i đến vị trí thứ j của dãy số nguyên A[1], A[2],
..., A[N] là:

trong đó

Yêu cầu: Cho dãy số nguyên A gồm N số A[1], A[2], ..., A[N] và số nguyên dương G (1 < G 2 < N). Hãy
chia dãy A thành đúng G đoạn để tổng trọng số là nhỏ nhất.
Dữ liệu vào trong file văn bản “LSS.INP” có dạng:
- Dòng đầu gồm hai số N và G (1 < G2 < N < 1001)
- N dòng tiếp theo, mỗi dòng một số nguyên mô tả dãy số A (0Kết quả ra file văn bản “LSS.OUT” có dạng: gồm một dòng chứa một số thực duy nhất là đáp án của bài
toán. (đưa ra theo qui cách :0:2)
LSS.INP
5 2
3
3
3
4
5

LSS.OUT
0.50

Phân tích
Gọi

là tổng trọng số của cách chia tối ưu các phần tử từ 1 đến
trọng số đoạn từ phần tử

Số bài toán con là
từ phần tử

. Chi phí chuyển trạng thái là
đến


tính này chỉ mất
const max
fi
fo
var

đoạn, khi đó

đến
, trong đó bao gồm việc tính trọng số đoạn

. Như vậy, độ phức tạp theo thuật toán trên là

ứng được với
đoạn từ phần tử

thành

và không đáp

Có thể giảm độ phức tạp thuật toán bằng cách tối ưu công thức tính trọng số
đến

. Thay vì phải tính trọng số đoạn từ phần tử

, khi đó độ phức tạp thuật toán chỉ còn
=1000;
='lss.inp';
='lss.out';


a
f
sum,sum2
n,g

:array[1..max]of extended;
:array[1..max,1..35]of extended;
:array[0..max]of extended;
:longint;

procedure docf;
var i
:longint;
begin
readln(n,g);
for i:=1 to n do begin
readln(a[i]);
8

đến

mất

thì việc


sum[i]:=sum[i-1]+a[i];
sum2[i]:=sum2[i-1]+ a[i] * a[i];
end;
end;

procedure qhd;
var i,j,k,soNhom :longint;
p
:extended;
begin
for i:=1 to n do
for j:=1 to g do f[i,j]:=1e100;
for i:=1 to n do f[i,1]:=sum2[i]-sqr(sum[i])/i;
for i:=2 to n do begin
soNhom:=i;
if i>g then soNhom:=g;
for j:=2 to soNhom do
for k:=i downto j do begin
p:=(sum2[i]-sum2[k-1])-sqr(sum[i]-sum[k-1])/(i-k+1);
if f[i,j]>p+f[k-1,j-1] then f[i,j]:=p+f[k-1,j-1];
end;
end;
end;
BEGIN
assign(input,fi); reset(input);
assign(output,fo); rewrite(output);
docf;
qhd;
writeln(f[n,g]:0:2);
close(input); close(output);
END.
5.4. TKSEQ
Cho dãy số A gồm N số nguyên và số nguyên M. Tìm dãy chỉ số 1≤i1
đạt giá trị lớn nhất.

Dữ liệu vào trong file “TKSEQ.INP” có dạng:
- Dòng đầu là gồm 2 số nguyên N, M (0<3M≤N≤500)
- Dòng 2 gồm N số nguyên a1, a2,…, aN (|ai|<109)
Kết quả ra file “TKSEQ.OUT” có dạng: gồm một số duy nhất S lớn nhất tìm được
TKSEQ.INP

TKSEQ.OUT
9


5 1
1 2 3 4 5

4

Phân tích
Yêu cầu bài toán là chọn ra 3M phần tử trong N phần tử và chia thành M nhóm. Tương tự như bài 3.5.3, ta
gọi

là tổng theo cách chọn tối ưu

nhóm trong các phần tử từ 1 đến

+ tổng tối ưu cách chọn 3 chỉ số trong đoạn từ phần tử
Số bài toán con là

. Chi phí chuyển trạng thái mất

3 chỉ số trong đoạn từ phần tử
tử


đến

đến

đến

nhân với chi phí tính tổng tối ưu cách chọn

. Việc tính tổng tối ưu cách chọn 3 chỉ số trong đoạn từ phần

var

phần tử trong

.

.

Tuy nhiên, ta có thể thay đổi cách nhìn và có được lời giải đơn giản ngắn gọn hơn. Gọi

trong đó
const

.

có thể chuẩn bị trước và lưu trữ vào mảng hai chiều, khi đó việc tính tổng chỉ còn

Độ phức tạp thuật toán là:
chọn ra


, khi đó

phần tủ đầu tiên của dãy,
.

max
fi
fo
limit
cs

=500;
='tkseq.inp';
='tkseq.out';
=trunc(1e13);
:array[0..2]of int64=(1,1,-1);

a
l
n,m
f

:array[1..max]of int64;
:array[0..max,0..max]of int64;
:longint;
:text;

procedure docf;
var i :longint;

begin
assign(f,fi); reset(f);
readln(f,n,m);
for i:=1 to n do read(f,a[i]);
close(f);
end;
procedure tinh;
var i,j,k,mm
:longint;
p
:int64;
begin
m:=m*3;
for i:=0 to n do
for j:=0 to m do l[i,j]:=-limit;
for i:=0 to n do l[i,0]:=0;
for i:=1 to n do begin
if m>i then mm:=i else mm:=m;
10

là tổng
,


for j:=1 to mm do
for k:=i downto j do
if l[i,j]l[i,j]:=l[k-1,j-1]+a[k]*cs[j mod 3];
end;
assign(f,fo); rewrite(f);

write(f,l[n,m]);
close(f);
end;
BEGIN
docf;
tinh;
END.
5.5. Kỷ lục đổ DOMINO
Một kỷ lục thế giới về xếp domino đổ đã được
ghi nhận vào hôm 17/11/2006. Kỷ lục này
thuộc về Hà Lan khi 4.079.381 quân domino
đã lần lượt đổ xuống theo phản ứng dây
chuyền trong tiếng vỗ tay reo hò của các cổ
động viên. Những người tổ chức sự kiện Ngày
Domino ở Hà Lan cho biết, 4.079.381 quân
domino đã lần lượt đổ xuống trong vòng 2 giờ
đồng hồ.

Những quân domino đã di động uyển chuyển trên nền những điệu nhạc cổ điển và đương đại là nét đặc
biệt nhất của màn trình diễn domino. Tác giả Robin Paul Weijers nói: “Hơn 4 triệu quân domino, điều
này chưa bao giờ xảy ra. Chúng tôi còn thành công trong việc khiến cho những quân bài domino nhảy
múa trong tiếng nhạc. Tôi rất hạnh phúc vì đã thành công.”
Với màn trình diễn tuyệt vời này, những kỷ lục gia domino Hà Lan đã phá vỡ kỷ lục của chính họ lập
được năm 2005 với 4.002.136 quân bài domino.
Sắp tới, Bờm dự định xây dựng một công trình lớn hơn để phá kỷ lục của người Hà Lan. Công trình sẽ bao
gồm 2 công đoạn chính:
-

Công đoạn 1: Xếp


quân domino vào các ô còn trống trên hình chữ nhật kích thức
, trong hình chữ nhật đó có

-

Công đoạn 2: Xếp

ô đã được đặt trước

quân domino thành một dãy độ dài

quân (có thể được hiểu như xếp vào hình chữ nhật kích thước

vật trang trí.
, mỗi hàng có đúng
).

Điểm độc đáo trong công trình này là sự phối màu giữa các quân domino lân cận chung cạnh. Các quân
domino được xếp bằng hai loại domino, loại 1 có màu xanh nhạt và loại 2 có màu xanh đậm. Quân
11


domino ở vị trí ô

sẽ phải thỏa mãn điều kiện: nếu

lẻ thì màu quân domino này sẽ phải có màu

không nhạt hơn các quân ở các ô chung cạnh (nếu có), nếu
có màu không đậm hơn các quân ở các ô chung cạnh (nếu có).


chẵn thì màu quân domino này sẽ phải

Để có những thông tin thú vị khi giới thiệu về công trình, Bờm muốn biết số lượng cách xếp khác nhau
của công đoạn 1 và công đoạn 2. Hai cách xếp được gọi là khác nhau nếu khi chồng khít 2 cách lên nhau
(không xoay hoặc lật) có ít nhất một quân khác màu.
Dữ liệu vào trong file “DOMINO.INP” có dạng:
-

Dòng 1: gồm 1 số nguyên dương

-

Dòng 2: bắt đầu là 3 số nguyên dương

, các kết quả tìm được sẽ mod cho

, trong đó

kích thước hình chữ nhật trong công đoạn 1,
trí, tiếp theo là
-

cặp số, cặp số

;


là số lượng ô trong hình chữ nhật đã đặt vật trang


là tọa độ ô đã đặt vật trang trí;

Dòng 3: gồm 2 số nguyên dương

là kích thước hình của công đoạn 2.

Kết quả ra file “DOMINO.OUT” có dạng:
-

Dòng 1: số cách xếp công đoạn 1 khác nhau mod

;

-

Dòng 2: số cách xếp công đoạn 2 khác nhau mod

.

DOMINO.INP
1000
5 5 1 3 3
3 10000

DOMINO.OUT
240
593

Phân tích
Cách giải cho phần 1: QHĐ với trạng thái

dưới, từ trái qua phải) với
ô cuối ở dòng

là dãy gồm

có nghĩa là xếp đến ô
bit 0, 1 mô tả trạng thái

. Để thuận tiện lưu trữ,

mà trong biểu diễn nhị phân của
Cách giải cho phần 2: QHĐ với trạng thái

là dãy

const

MAXN
MAXN2
MAXSL
fi
fo

=8;
=16;
=55;
='DOMINO.INP';
='DOMINO.OUT';
12


ô đầu ở dòng



sẽ được mã hoá là một số nguyên
.

có nghĩa là xếp đến hàng

với là dãy
bit 0, 1 thỏa mãn tích chất màu trên một dòng (với
để tính nhanh.

(xếp lần lượt từ trên xuống

=8 thì

có trạng thái hàng trước là
<=55). Dùng nhân ma trận


type

dsType
=array[1..MAXN]of longint;
matrixType =array[1..MAXSL,1..MAXSL]of longint;

var

m,n,nMod,SL:longint;

ds
:array[1..2,1..1 shl MAXN]of dsType;
count
:array[1..2]of longint;
A, B, AB
:matrixType;
x
:dsType;
sum
:longint;
f,g
:text;
dp
:array[1..MAXN2+1,1..MAXN2,0..1 shl MAXN2]of
longint;
w
:array[1..MAXN2,1..MAXN2]of longint;
mu2
:array[1..MAXN2]of longint;
function check(t:longint):boolean;
var i :longint;
begin
for i:=2 to n do begin
if ((i+t) mod 2 = 1)and(x[i]if ((i+t) mod 2 = 0)and(x[i]>x[i-1]) then exit(false);
end;
exit(true);
end;
procedure try(i :longint);
var c : longint;

begin
if i>n then begin
for c:=1 to 2 do
if check(c) then begin
inc(count[c]); ds[c][count[c]]:=x;
end;
end
else begin
for c:=0 to 1 do
begin
x[i]:=c;
try(i+1);
end;
end;
end;
function ghep(j1,j2,i:longint):boolean;
var j :longint;
begin
for j:=1 to n do begin
if ((i+1+j) mod 2=1)and(ds[(i+1) mod 2+1][j1][j]>ds[i
mod 2+1][j2][j]) then exit(false);
if ((i+1+j) mod 2=0)and(ds[(i+1) mod 2+1][j1][j]mod 2+1][j2][j]) then exit(false);
13


end;
exit(true);
end;
function mulMatrix(X, Y: matrixType):matrixType;

var i,j,k :longint;
Z
:matrixType;
begin
fillchar(Z,sizeof(Z),0);
for i:=1 to SL do
for j:=1 to SL do
for k:=1 to SL do Z[i,j]:=(Z[i,j]+int64(X[i,k])*Y[k,j])
mod nMod;
exit(Z);
end;
function expMatrix(X: matrixType; e:longint):matrixType;
var Y
:matrixType;
begin
if e=1 then exit(X);
Y:=expMatrix(X,e div 2);
Y:=mulMatrix(Y,Y);
if e mod 2 = 1 then Y:=mulMatrix(Y,X);
exit(Y);
end;
procedure chuanbi;
var i,j :longint;
begin
count[1]:=0; count[2]:=0;
try(1);
SL:=count[1];
fillchar(A,sizeof(A),0);
for i:=1 to SL do
for j:=1 to SL do

A[i,j]:=ord(ghep(i,j,1));
fillchar(B,sizeof(B),0);
for i:=1 to SL do
for j:=1 to SL do
B[i,j]:=ord(ghep(i,j,2));
AB:=mulMatrix(A,B);
end;
procedure qhd2;
var i,j :longint;
begin
14


m:=m-1;
if m>1 then begin
AB:=expMatrix(AB,m div 2);
if m mod 2 = 1 then AB:=mulMatrix(AB,A);
sum:=0;
for i:=1 to SL do
for j:=1 to SL do sum:=(sum + AB[i,j]) mod nMod;
end
else sum:=SL;
end;
procedure xuly2;
begin
readln(f,n,m);
chuanbi;
qhd2;
writeln(g,sum);
end;

function check1(i,j,t,c:longint):boolean;
begin
if (i+j) mod 2 = 1 then begin
if (j>1)and(c=0)and(t and mu2[j-1]>0)and(w[i,j-1]=0)
then exit(false);
if (i>1)and(c=0)and(t and mu2[j]>0)and(w[i-1,j]=0) then
exit(false);
end
else begin
if (j>1)and(c=1)and(t and mu2[j-1]=0)and(w[i,j-1]=0)
then exit(false);
if (i>1)and(c=1)and(t and mu2[j]=0)and(w[i-1,j]=0) then
exit(false);
end;
exit(true);
end;
function tinh(i,j,t :longint):longint;
var c
: longint;
sum : longint;
begin
if dp[i,j,t]>=0 then exit(dp[i,j,t]);
if i>m then begin
dp[i,j,t]:=1;
exit(1);
15


end;
if w[i,j]=1 then begin

if jelse dp[i,j,t]:=tinh(i+1,1,t);
exit(dp[i,j,t]);
end;
sum:=0;
for c:=0 to 1 do
if check1(i,j,t,c) then
begin
if jelse sum:=sum + tinh(i+1,1,(t or mu2[j])-(1c)*mu2[j] );
end;
dp[i,j,t]:=sum mod nMod;
exit(dp[i,j,t]);
end;
procedure xuly1;
var i,j,k,T :longint;
begin
fillchar(w,sizeof(w),0);
read(f,m,n,T);
for k:=1 to T do begin
read(f,i,j);
w[i,j]:=1;
end;
mu2[1]:=1;
for k:=2 to MAXN2 do mu2[k]:=mu2[k-1]*2;
fillchar(dp,sizeof(dp),255);
writeln(g,tinh(1,1,0));
end;
BEGIN
assign(f,fi); reset(f);

assign(g,fo); rewrite(g);
readln(f,nMod);
xuly1;
xuly2;
16


close(f);
close(g);
END.
5.6. Cổ phiếu (VOI2010_R2)
Hiện nay thị trường chứng khoán Việt Nam là một kênh huy động vốn hiệu quả cho các doanh nghiệp
cũng như là một kênh đầu tư tài chính tiềm năng cho các nhà đầu tư. Một nhà đầu tư muốn thực hiện việc
mua và bán cổ phiếu của n công ty tin học có niêm yết cổ phiếu trên thị trường trong giai đoạn gồm D
ngày.
Sau nhiều ngày phân tích, nhà đầu tư rút ra được các dự báo sau:
1.

Số lượng cổ phiếu của công ty i (1 ≤ i ≤ n) trên thị trường là Qi;

2.

Giá một cổ phiếu của công ty i trong ngày t là Pt,i (0 < Pt,i ≤ 106);

3.

Phí giao dịch mua bán một cổ phiếu là 1 đồng.

Luật đầu tư như sau: Tại một ngày t (1 ≤ t ≤ D) nhà đầu tư có thể tiến hành hai bước:


Bước 1 – Bán cổ phiếu: Nếu có mi cổ phiếu của công ty i trong tài khoản, nhà đầu tư có
thể tiến hành bán si (0 ≤ si ≤ mi) cổ phiếu. Số tiền bán cổ phiếu trừ đi phí giao dịch (si × Pt,i − si) sẽ
được chuyển vào tài khoản của nhà đầu tư ngay lập tức. Số lượng cổ phiếu của công ty i trong tài
khoản của nhà đầu tư còn lại là (mi – si). Với mỗi loại cổ phiếu, nhà đầu tư có thể bán hoặc không
bán cổ phiếu.

Bước 2 – Mua cổ phiếu (thực hiện sau khi tiến hành xong Bước 1): Nếu muốn mua si cổ
phiếu của công ty i, nhà đầu tư phải trả ngay lập tức tiền mua cổ phiếu và phí giao dịch (vì thế
muốn thực hiện giao dịch này, số tiền trong tài khoản phải lớn hơn hoặc bằng số tiền phải trả). Số
tiền trong tài khoản của nhà đầu tư ngay lập tức sẽ bị trừ đi một lượng là (si × Pt,i + si) đồng. Số
lượng cổ phiếu mua ngay lập tức sẽ được chuyển vào tài khoản của nhà đầu tư. Chú ý: tổng số cổ
phiếu của công ty i trong tài khoản của nhà đầu tư không thể lớn hơn Qi. Với mỗi loại cổ phiếu,
nhà đầu tư có thể mua hoặc không mua cổ phiếu.
Yêu cầu: Tại thời điểm đầu tiên, nhà đầu tư có k đồng trong tài khoản, và không có cổ phiếu của bất cứ
công ty nào. Dựa vào dự báo nêu trên, hãy giúp nhà đầu tư tìm cách mua bán cổ phiếu từ ngày thứ nhất
cho đến hết ngày thứ D, để tổng lượng tiền thu được là lớn nhất.
Dữ liệu: Vào từ file văn bản STOCK.INP:


Dòng đầu chứa 3 số nguyên dương n, k, D (n ≤ 3, k ≤ 109, D ≤ 30);


Dòng thứ hai chứa n số nguyên dương Q1, Q2,…, Qn cho biết số lượng cổ phiếu của các
công ty niêm yết trên thị trường (Qi ≤ 100, i = 1, 2,…, n);

Dòng thứ t trong D dòng tiếp theo chứa n số nguyên dương cho biết giá cổ phiếu của n
công ty tại ngày t.
Các số trên cùng một dòng được ghi cách nhau bởi ít nhất một dấu cách.
Kết quả: Ghi ra file văn bản STOCK.OUT một số nguyên dương duy nhất là tổng lượng tiền nhà đầu tư
có được sau D ngày theo cách mua bán cổ phiếu tìm được.

Ví dụ:
STOCK.INP
2 100 5
15 20

STOCK.OUT
255
17


4
5
7
6
5

5
4
3
10
12

Tìm hiểu chương trình giải sau (sẽ được trao đổi khi tập huấn).
const max
=100;
fi
='stock.inp';
fo
='stock.out';
var


l
q
cost
n,m,t

:array[0..max,0..max,0..max]of int64;
:array[1..3]of longint;
:array[1..10000,1..3]of int64;
:int64;

procedure ReadIn;
var f
:text;
i,j
:longint;
begin
assign(f,fi); reset(f);
readln(f,n,m,t);
for i:=1 to n do read(f,q[i]);
for i:=1 to t do
for j:=1 to n do read(f,cost[i,j]);
close(f);
for i:=n+1 to 3 do q[i]:=0;
n:=3;
end;
procedure Process;
var i1,i2,i3,k :longint;
money
:int64;

begin
for i1:=0 to q[1] do
for i2:=0 to q[2] do
for i3:=0 to q[3] do l[i1,i2,i3]:=-1;
l[0,0,0]:=m;
for k:=1 to t do begin
// ban co phieu
for i1:=q[1] downto 0 do
for i2:=q[2] downto 0 do
for i3:=q[3] downto 0 do begin
money:=l[i1,i2,i3];
if money > -1 then
begin
18


if (i1>0) and (l[i1-1,i2,i3] < money+cost[k,1]-1)
then
l[i1-1,i2,i3] := money+cost[k,1]-1;
if (i2>0) and (l[i1,i2-1,i3] < money+cost[k,2]-1)
then
l[i1,i2-1,i3] := money+cost[k,2]-1;
if (i3>0) and (l[i1,i2,i3-1] < money+cost[k,3]-1)
then
l[i1,i2,i3-1] := money+cost[k,3]-1;
end;
end;
// mua co phieu
for i1:=0 to q[1] do
for i2:=0 to q[2] do

for i3:=0 to q[3] do begin
money:=l[i1,i2,i3];
if money > -1 then
begin
if (i1<q[1]) and (money > cost[k,1]) and
(l[i1+1,i2,i3] < money-cost[k,1]-1) then
l[i1+1,i2,i3] := money-cost[k,1]-1;
if (i2<q[2]) and (money > cost[k,2]) and
(l[i1,i2+1,i3] < money-cost[k,2]-1) then
l[i1,i2+1,i3] := money-cost[k,2]-1;
if (i3<q[3]) and (money > cost[k,3]) and
(l[i1,i2,i3+1] < money-cost[k,3]-1) then
l[i1,i2,i3+1] := money-cost[k,3]-1;
end;
end;
end;
end;
procedure WriteOut;
var f :text;
begin
assign(f,fo); rewrite(f);
write(f,l[0,0,0]);
close(f);
end;
BEGIN
ReadIn;
Process;
WriteOut;
19



END.
5.7. VACCINE (ACMVN2010)
Amino acid là thành phần cơ bản của sự sống. Amino acid gồm 20 loại khác nhau {A,C,D, E, F, G, H, I,
K, L, M, N, P, Q, R, S, T, V, W, Y}. Protein là một chuỗi amino acid và được biểu bởi một xâu kí tự P,
trong đó P(t) là amino acid ở vị trí thứ t trong P. Các chuỗi amino acid khác nhau sẽ tạo ra các protein
khác nhau. M-vaccine là một loại protein có độ dài không quá 500 amino acid được dùng để tiêu diệt các
loại vi rút.
Các nhà khoa học đang tập trung nghiên cứu protein của vi rút POKTE nhằm sản xuất ra các loại Mvaccine để tiêu diệt loại vi rút này.
Ta nói, M-vaccine V có thể tiêu diệt được k amino acid trong protein P của vi rút POKTE nếu tồn hai dãy
chỉ số (X1 < X2 <…POKTE của M-vaccine V được xác định bằng giá trị k lớn nhất.
Cho protein P của vi rút POKTE và N loại M-vaccine, nhiệm vụ của bạn là tìm loại M-vaccine trong N
loại M-vaccine có mức độ tiêu diệt vi rút POKTE lớn nhất.
Dữ liệu vào (VACCINE.INP)
Dữ liệu vào gồm nhiều bộ dữ liệu tương ứng với nhiều test. Dòng đầu tiên chứa một số nguyên dương
không lớn hơn 20 là số lượng các bộ dữ liệu. Các dòng tiếp theo chứa các bộ dữ liệu.
Với mỗi bộ dữ liệu, dòng đầu tiên chứa số nguyên N (1 ≤ N ≤ 200) là số lượng loại M-vaccine. Dòng thứ
hai chứa một xâu độ dài không vượt quá 10000 là biểu diễn protein P của vi rút POKTE. N dòng tiếp theo,
mỗi dòng chứa một xâu biểu diễn cho một loại M-vaccine.
Dữ liệu ra (VACCINE.OUT)
Với mỗi bộ dữ liệu, ghi ra trên một dòng một số nguyên là mức độ tiêu diệt của loại M-vaccine có mức độ
tiêu diệt vi rút POKTE lớn nhất.
VACCINE.INP
1
5 1
ENRPPNVPES
TEV
LNRC
HKVR

FWW
PWP

VACCINE.OUT
2

Tìm hiểu chương trình giải sau (sẽ được trao đổi khi tập huấn).
{$H+}
const MAXM
MAXN
fi

=500;
=10000;
='VIRUS.INP';
20


fo
var

='VIRUS.OUT';

w
:array[0..MAXM+1,0..MAXM+1]of longint;
next :array[0..MAXN+1,'A'..'Z']of longint;
a,b
:string;
f,g
:text;

na,nb,T,alpha
:longint;
mk,mkMin
:longint;
count
:longint;
c
:char;
nTest
:longint;
tong,kq
:longint;

procedure makeNext(c1,c2:char);
var c :char; j:longint;
begin
fillchar(next,sizeof(next),0);
for c:=c1 to c2 do begin
next[nb,c]:=nb+1; next[nb+1,c]:=nb+1;
for j:=nb-1 downto 0 do
if b[j+1]=c then next[j,c]:=j+1
else next[j,c]:=next[j+1,c];
end;
end;
function tinh:longint;
var i,j :longint;
begin
for i:=0 to na do begin
for j:=0 to na do w[i,j]:=nb+1;
w[i,0]:=0;

end;
mk:=0;
for i:=0 to na-1 do begin
if alpha+1-na+i>0 then mkMin:=alpha+1-na+i
else mkMin:=0;
for j:=mk downto mkMin do begin
if w[i+1,j]>w[i,j] then w[i+1,j]:=w[i,j];
if w[i+1,j+1]>next[w[i,j],a[i+1]] then begin
if j+1>mk then mk:=j+1;
w[i+1,j+1]:=next[w[i,j],a[i+1]];
end;
end;
end;
21


exit(mk);
end;
BEGIN
assign(f,fi); reset(f);
assign(g,fo); rewrite(g);
readln(f,nTest);
while nTest>0 do begin
dec(nTest); tong:=0;
count:=0;
alpha:=0;
readln(f,T);
readln(f,b);
nb:=length(b);
makeNext('A','Z');

while T>0 do begin
readln(f,a); na:=length(a);
dec(T);
kq:=tinh;
if kq>alpha then alpha:=kq;
end;
writeln(g,alpha);
end;
close(f);
close(g);
END.
5.8. Bảng số (VOIR2_2012)
Giả sử A là lưới ô vuông gồm m dòng và n cột. Các dòng của lưới được đánh số từ 1 đến m, từ trên
xuống dưới. Các cột của lưới được đánh số từ 1 đến n, từ trái sang phải. Ô nằm trên giao của dòng i và cột
j của lưới gọi là ô (i, j).
Với số nguyên dương x, gọi f(x) là số lượng số nguyên dương không vượt quá x mà trong biểu diễn
nhị phân có hai bít 1 đứng cạnh nhau. Ví dụ, f(5)=1 vì trong các số nguyên dương bé hơn hoặc bằng 5 chỉ
có số 3 có biểu diễn nhị phân với hai bít 1 đứng cạnh nhau.
Cho dãy số nguyên dương gồm m×n số b1, b2, ..., bm×n. Ta sẽ lần lượt điền các số hạng của dãy
22


f(b1) mod 3, f(b2) mod 3, ..., f(bm×n) mod 3
vào các ô của lưới A theo thứ tự từ trên xuống dưới từ trái qua phải. Gọi bảng số thu được là B.
Xét truy vấn sau đây đối với bảng số thu được B: Cho hai số nguyên p và q (1 ≤ p ≤ q ≤ m), hãy
cho biết diện tích lớn nhất của hình chữ nhật gồm các ô nằm trong phạm vi từ dòng thứ p đến dòng thứ q
của bảng B mà trong đó chênh lệch giữa phần tử lớn nhất và phần tử nhỏ nhất không vượt quá 1.
Yêu cầu: Cho m, n, dãy số b1, b2, ..., bm×n và k bộ pi, qi (i = 1, 2, ..., k) tương ứng với k truy vấn, hãy đưa ra
các câu trả lời cho k truy vấn.
Dữ liệu: Vào từ file văn bản NUMTAB.INP trong đó:

• Dòng đầu tiên chứa hai số nguyên m, n (1 ≤ m, n ≤ 1000);
• Dòng tiếp theo chứa dãy số b1, b2, ..., bm×n (mỗi số không vượt quá 109);
• Dòng tiếp theo chứa số nguyên k (1 ≤ k ≤ 106);
• Dòng thứ i trong số k dòng tiếp theo chứa 2 số nguyên pi và qi (i = 1, 2, ..., k).
Hai số liên tiếp trên cùng một dòng được ghi cách nhau bởi dấu cách.
Kết quả: Ghi ra file văn bản NUMTAB.OUT gồm k dòng, mỗi dòng chứa một số là câu trả lời cho truy
vấn theo thứ tự xuất hiện trong dữ liệu vào.
Ví dụ:
NUMTAB.INP
3 3
3 8 7 6 3 2 4 6 6
4
1 1
1 2
1 3
3 3

NUMTAB.OUT
3
4
4
3

Tìm hiểu chương trình giải sau (sẽ được trao đổi khi tập huấn).
#include <iostream>
#include <fstream>
using namespace std;
const int MAX = 1000+5;
int
int

int
int
int
int
int

a[MAX][MAX], m, n;
kq01[MAX][MAX], kq12[MAX][MAX], w[MAX][MAX];
xLen, xArr[32];
fx[20][2][2][2][2];
fy[1<<15][2][2][2][2];
dx[20][2][2][2][2];
dy[1<<15][2][2][2][2];

int qhd(int pos, int firstDigit, int firstOk, int lastDigit, int
lastOk) {
int &res = fx[pos][firstDigit][firstOk][lastDigit][lastOk];
if (dx[pos][firstDigit][firstOk][lastDigit][lastOk]) return
23


res;
if (pos>15) return ((firstDigit == lastDigit) && (firstOk ==
lastOk));
res = 0;
for (int cs = 0; cs<=max(xArr[pos],firstOk); cs++)
if (firstDigit+cs!=2)
res += qhd(pos+1, cs, firstOk | (cslastDigit, lastOk);
dx[pos][firstDigit][firstOk][lastDigit][lastOk] = 1;

return res;
}
int tinhSL15(int x, int firstDigit, int firstOk, int lastDigit,
int lastOk) {
int &res = fy[x][firstDigit][firstOk][lastDigit][lastOk];
if (dy[x][firstDigit][firstOk][lastDigit][lastOk]) return
res;
dy[x][firstDigit][firstOk][lastDigit][lastOk] = 1;
xLen = 15;
for (int i=1; i<=15; i++) {
xArr[xLen] = x & 1;
xLen--;
x = x >> 1;
}
memset(fx,0,sizeof(fx));
memset(dx,0,sizeof(dx));
return (res = (qhd(1, firstDigit, firstOk, lastDigit,
lastOk)));
}
int tinhSL(int x) {
int xA = x >> 15, xB = x & ((1<<15)-1);
int res = 0;
for (int digit=0; digit<=1; digit++)
for (int ok=0; ok<=1; ok++) {
int tmp = tinhSL15(xB, digit, ok, 0, 0);
tmp += tinhSL15(xB, digit, ok, 0, 1);
tmp += tinhSL15(xB, digit, ok, 1, 0);
tmp += tinhSL15(xB, digit, ok, 1, 1);
res = res + tinhSL15(xA, 0, 0, digit, ok )*tmp;
}

return (x+1 - res);
}
void docFile() {
24


int x;
memset(fy,0,sizeof(fy));
memset(dy,0,sizeof(dy));
scanf("%d%d",&m,&n);
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++) {
scanf("%d",&x);
a[i][j] = tinhSL(x) % 3;
}
}
void chuanBi(int A, int B, int kq[MAX][MAX]) {
int s[MAX], p[MAX], h[MAX], top;
memset(h,0,sizeof(h));
memset(p,0,sizeof(p));
memset(kq,0,sizeof(kq));
memset(w,0,sizeof(w));
memset(s,0,sizeof(s));
for (int i=1; i<=m; i++) {
//tinh do cao
for (int j=1; j<=n; j++)
if ((a[i][j]>=A)&&(a[i][j]<=B)) h[j]++;
else h[j] = 0;
top = 0;
for (int j=1; j<=n+1; j++) {

if (h[j]>s[top]) {
top++;
s[top] = h[j];
p[top] = j;
continue;
}
while (s[top]>h[j]) {
w[i][s[top]] = max(w[i][s[top]],s[top] * (jp[top]));
top--;
}
if (h[j]>s[top]) {
top++;
s[top] = h[j];
}
}
// tinh cac do cao con lai
25


×