5.3 Cắt hình
Việt Nam, 2008
Một tấm bìa dạng lưới vuông cạnh dài n = 2k tạo bởi các ô vuông đơn vị đánh số theo dòng 1.. n tính từ trên xuống và theo cột 1..n tính từ trái sang. Người ta thực hiện lần lượt hai thao tác đan xen nhau sau đây cho đến khi nhận được một cột gồm n2 ô vuông đơn vị:
1. Cắt ngang hình theo đường kẻ giữa sau đó chồng nửa trên lên trên nửa dưới;
2. Cắt dọc hình theo đường kẻ giữa sau đó chồng nửa trái lên trên nửa phải.
Tại cột cuối cùng người ta đánh số các ô vuông đơn vị 1, 2,..., n2 tính từ trên xuống.
Hãy viết hai thủ tục sau đây
a) ViTri(k, i, j) = v cho ra số thứ tự v của ô (i,j) trên cột cuối cùng.
b) ToaDo(k, v, i, j) Cho trước k và vị trí v trên cột cuối cùng, tính tọa độ (i,j) của ô ban đầu.
Thí dụ
ViTri(2, 4, 3) = 8.
ToaDo(2,8,i, j) cho kết quả i = 4, j = 3.
Thuật toán
Ta khảo sát bài toán với k = 2. Ta có n = 2k = 22 = 4. Ta kí hiệu ô nằm trên dòng i, cột j là [i,j].
A
|
C
|
Tầng 1
|
[1,1] [1,2] [1,3] [1,4]
[2,1] [2,2] [2,3] [2,4]
[3,1] [3,2] [3,3] [3,4]
[4,1] [4,2] [4,3] [4,4]
|
B
|
D
|
4 mảnh thu được sau một lần ND
|
|
Trước khi cắt cột có duy nhất 1 tầng
| Nhận xét: Ta gọi một lần cắt ngang và một lần cắt dọc liên tiếp nhau là ND. Sau mỗi lần ND ta thu được 4 mảnh vuông và bằng nhau A, B, C và D được chồng lên nhau thành một cột theo thứ tự tính từ trên xuống là A, B, C và D (mảnh A trên cùng, mảnh D dưới cùng). Gọi d là độ dày (số tầng) của khối này. Ta có, lúc đầu d = 1 và cột chỉ có 1 tầng gồm 1 tấm bìa duy nhất ban đầu. Gọi n là chiều dài cạnh của một mảnh hình vuông. Sau mỗi lần ND, n giảm 2 lần. Vị trí v của các ô đơn vị trong mảnh A sẽ được bảo lưu, trong mảnh B được cộng thêm d, mảnh C được cộng thêm 2d và mảnh D được cộng thêm 3d. Sau mỗi lần ND số tầng d sẽ tăng thêm 4 lần. Biết tọa độ (i,j) trong hình ABCD ta dễ dàng tính được tọa độ mới (i',j') trong từng mảnh.
Tầng 1
|
[1,1] [1,2] [1,3] [1,4]
[2,1] [2,2] [2,3] [2,4]
AC
|
Tầng 1: A
|
[1,1] [1,2]
[2,1] [2,2]
|
Tầng 2: B
|
[3,1] [3,2]
[4,1] [4,2]
|
Tầng 2
|
[3,1] [3,2] [3,3] [3,4]
[4,1] [4,2] [4,3] [4,4]
BD
|
Tầng 3: C
|
[1,3] [1,4]
[2,3] [2,4]
|
Tầng 4: D
|
[3,3] [3,4]
[4,3] [4,4]
|
Cắt ngang lần 1 và chồng nửa trên lên trên nửa dưới thu được 2 tầng
|
Cắt dọc lần 1 và chồng nửa trái lên trên nửa phải thu được 4 tầng
|
|
|
|
|
|
Tầng 1
|
[1,1] [1,2]
|
|
Tầng 1
|
[1,1]
|
Tầng 2
|
[3,1] [3,2]
|
|
Tầng 2
|
[3,1]
|
Tầng 3
|
[1,3] [1,4]
|
|
Tầng 3
|
[1,3]
|
Tầng 4
|
[3,3] [3,4]
|
|
Tầng 4
|
[3,3]
|
Tầng 5
|
[2,1] [2,2]
|
|
Tầng 5
|
[2,1]
|
Tầng 6
|
[4,1] [4,2]
|
|
Tầng 6
|
[4,1]
|
Tầng 7
|
[2,3] [2,4]
|
|
Tầng 7
|
[2,3]
|
Tầng 8
|
[4,3] [4,4]
|
|
Tầng 8
|
[4,3]
|
|
|
|
Tầng 9
|
[1,2]
|
Cắt ngang lần 2 và chồng nửa trên lên trên nửa dưới thu được 8 tầng
|
|
Tầng 10
|
[3,2]
|
|
Tầng 11
|
[1,4]
|
|
Tầng 12
|
[3,4]
|
|
|
|
Tầng 13
|
[2,2]
|
|
|
|
Tầng 14
|
[4,2]
|
|
|
|
Tầng 15
|
[2,4]
|
|
|
|
Tầng 16
|
[4,4]
|
|
|
|
|
|
|
|
|
Cắt dọc lần 2 và chồng nửa trái lên trên nửa phải
|
Ta chọn cách mã số các mảnh A, B, C và D một cách khôn ngoan. Cụ thể là ta gán mã số cho các mảnh này theo số lần cộng thêm độ dày d sau mỗi lần ND, tức là ta đặt A = 0, B = 1, C = 2 và D = 3. Trong dạng nhị phân ta có A = 002, B = 012, C = 102 và D = 112.
A
002 = 0
|
C
102 = 2
|
B
012 = 1
|
D
112 = 3
|
Sau mỗi lần ND ta thu được 4 mảnh A, B, C, D.
|
function ViTri(k,i,j: integer): integer;
var d, v, m, manh, n: integer;
begin
d := 1; { so tang }
v := 1; { tang chua o [i,j] }
n := 1 shl k; { n = 2^k }
for m := 1 to k do
begin
n := n div 2; manh := 0;
if (j > n) then
begin
manh := 2; j := j - n;
end;
if (i > n) then
begin
manh := manh + 1; i := i - n;
end;
v := v + manh*d; d := 4*d;
end;
ViTri := v;
end;
Thủ tục ToaDo là đối xứng với thủ tục ViTri.
procedure ToaDo(k,v: integer; var i,j: integer);
var n,nn,m,d, manh: integer;
begin
n := 1 shl k; d := n*n;
n := 1 ; { kich thuoc 1 tang }
i := 1; j := 1;
for m := 1 to k do
begin
d := d div 4;
manh := (v-1) div d;
if (manh >= 2) then j := j+n;
if odd(manh) then i := i+n;
v := v - manh*d; n := n * 2;
end;
end;
// DevC++
#include
using namespace std;
// P R O T O T Y P E S
int ViTri(int, int, int);
void ToaDo(int, int, int &, int &);
void Test(int);
// I M P L E M E N T A T I O N
int main() {
Test(2);
cout << endl;
system("PAUSE");
return EXIT_SUCCESS;
}
// Cho biet k va toa do (i,j). Tim vi tri tren cot
int ViTri(int k, int i, int j) {
int n = 1 << k; // kich thuoc hinh: n = 2^k
int d = 1; // be day 1 tang
int v = 1; // vi tri;
int manh;
for (int m = 0; m < k; ++m) {
n >>=1; // n = n/2
manh = 0;
if (j > n) { manh = 2; j -= n; }
if (i > n) { manh += 1; i -= n; }
v += manh * d; d *= 4;
}
return v;
}
// Cho vi tri o tren cot v, tinh toa do (i,j). n = 2^k
void ToaDo(int k, int v, int &i, int &j) {
int n = 1, d = 1 << k, manh = 0;
d *= d;
i = j = 1;
for (int m = 0; m < k; ++m) {
d >>= 2; // d /= 4;
manh = (v - 1) / d;
if (manh >= 2) j += n;
if (manh % 2 == 1) i += n;
v -= manh * d; n *= 2;
}
}
Độ phức tạp
Với n = 2k chương trình cần k lần lặp, mỗi lần lặp cần thực hiện không quá 10 phép toán số học trên các số nguyên.
Thủ tục Test dưới đây hiển thị vị trí v của mọi ô (i,j), i = 1..n, j = 1..n sau đó xuất phát từ vị trí v để tính lại tọa độ i, j.
(* Pascal *)
procedure Test(k: integer);
var n, v, ii, jj : integer;
begin
n := 1 shl k; { n = 2^k }
writeln; writeln( ' k = ', k, ' n = ', n);
for i := 1 to n do
for j := 1 to n do
begin
v := ViTri(k, i, j); ToaDo(k, v, ii, jj);
writeln; write('(',i, ',', j, ') => ', v);
writeln(' => ', '(', ii, ',', jj, ')');
readln;
end;
end;
// DevC++
void Test(int k) {
int n = 1 << k;
cout << endl << "k = " << k << " n = " << n
int v, ii, jj;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
v = ViTri(k, i, j); ToaDo(k, v, ii, jj);
cout << endl << "(" << i << "," << j << ") => "
<< v << " => (" << ii << "," << jj << ")";
cin.get();
}
}
Chia sẻ với bạn bè của bạn: |