Bài gộp



Bài gộp - gia sư tin học
               Bộ bài bao gồm n quân, được gán mã số từ 1 đến n. Lúc đầu bộ bài được chia cho n người, mỗi người nhận 1 quân. Mỗi lượt chơi, trọng tài chọn ngẫu nhiên hai số x và y trong khoảng 1..n. Nếu có hai người khác nhau, một người có trong tay quân bài x và người kia có quân bài y thì một trong hai người đó phải trao toàn bộ số bài của mình cho người kia theo nguyên tắc sau: mỗi người trong số hai người đó trình ra một quân bài tuỳ chọn của mình, Ai có quân bài mang mã số nhỏ hơn sẽ được nhận bài của người kia. Trò chơi kết thúc khi có một người cầm trong tay cả bộ bài. Biết số quân bài n và các quân bài trọng tài chọn ngẫu nhiên sau m lượt chơi,  hãy cho biết số lượng người còn có bài trên tay.
Dữ liệu vào: Tệp văn bản BAIGOP.INP.
               - Dòng đầu tiên: hai số n và m, trong đó n là số lượng quân bài trong bộ bài, m là số lần trọng tài chọn ngẫu nhiên hai số x và y. Các quân bài được gán mã số từ 1 đến n. Mã số này được ghi trên quân bài.
               - Tiếp đến là m dòng, mỗi dòng ghi hai số tự nhiên x và y do trọng tài cung cấp. Các số trên cùng một dòng cách nhau qua dấu cách.
               Dữ liệu ra: Hiển thị trên màn hình số lượng người còn có bài trên tay.
Thí dụ:
Dữ liệu vào: 
BAIGOP.INP
Kết quả hiển thị trên màn hình
 ý nghĩa: bộ bài có 10 quân mã số lần lượt 1, 2,…, 10 và có 10 người chơi. Sáu lần trọng tài chọn ngẫu nhiên các cặp số (x, y) là (2, 5),
(3, 3), (4, 7), (1, 5), (2, 8) và (9, 3). Cuối ván chơi còn lại 5 người có bài trên tay: {1,
 2, 5, 8}, {3, 9},  {4, 7}, {6}, {10}.

10 6
2 5
3 3
4 7
1 5
2 8
9 3
5

Thuật toán
Đây là bài toán có nhiều ứng dụng hữu hiệu nên bạn đọc cần tìm hiểu kĩ và cố gắng cài đặt cho nhuần nhuyễn. Như sau này sẽ thấy, nhiều thuật toán xử lí đồ thị như tìm cây khung, xác định thành phần liên thông, xác định chu trình… sẽ phải vận dụng cách tổ chức dữ liệu tương tự như thuật toán sẽ trình bày dưới đây.
Bài này đòi hỏi tổ chức các tập quân bài sao cho thực hiện nhanh nhất các thao tác sau đây:
Find(x): cho biết tên của tập chứa phần tử x.
Union(x, y): hợp tập chứa x với tập chứa y.
Mỗi tập là nhóm các quân bài có trong tay một người chơi. Như vậy mỗi tập là một tập con của bộ bài {1, 2,…, n}. Ta gọi bộ bài là tập chủ hay tập nền. Do tính chất của trò chơi, ta có hai nhận xét quan trọng sau đây:
1.       Hợp của tất cả các tập con (mỗi tập con này do một người đang chơi quản lí) đúng bằng tập chủ.
2.       Hai tập con khác nhau không giao nhau: tại mỗi thời điểm của cuộc chơi, mỗi quân bài nằm trong tay đúng một người.
Họ các tập con thỏa hai tính chất nói trên được gọi là một phân hoạch của tập chủ.
Các thao tác nói trên phục vụ trực tiếp cho việc tổ chức trò chơi theo sơ đồ sau:
Khởi trị:
for i:= 1 to n do
begin
       Trọng tài sinh ngẫu nhiên hai số x và y
   trong khoảng 1..n:
   Hợp tập chứa x với tập chứa y: Union(x,y);
end;
Để thực hiện thủ tục Union(x,y) trước hết ta cần biết quân bài x và quân bài y đang ở trong tay ai? Sau đó ta cần biết người giữ quân bài x (hoặc y) có quân bài nhỏ nhất là gì? Quân bài nhỏ nhất được xác định trong toàn bộ các quân bài mà người đó có trong tay. Đây chính là điểm dễ nhầm lẫn. Thí dụ, người chơi A đang giữ trong tay các quân bài 3, 4 và 7, A = {3, 4, 7}; người chơi B đang giữ các quân bài 2, 5, 9 và 11, B = {2, 5, 9, 11}. Các số gạch chân là số hiệu của quân bài nhỏ nhất trong tay mỗi người.  Nếu x= 9 và y = 7 thì A (đang giữ quân y = 7) và B (đang giữ quân x = 9) sẽ phải đấu với nhau. Vì trong tay A có quân nhỏ nhất là 3 và trong tay B có quân nhỏ nhất là 2 nên A sẽ phải nộp bài cho B và ra khỏi cuộc chơi. Ta có, B = {2, 3, 4, 5, 7, 9, 11}. Ta kết hợp việc xác định quân bài xtrong tay ai và người đó có quân bài nhỏ nhất là bao nhiêu làm một để xây dựng hàm Find(x). Cụ thể là hàm Find(x) sẽ cho ta quân bài nhỏ nhất có trong tay người giữ quân bài x. Trong thí dụ trên ta có:
Find(x) = Find(9) = 2 và Find(y) = Find(7) = 3
Lưu ý rằng hàm Find(x) không chỉ rõ ai là người đang giữ quân bài x mà cho biết quân bài có số hiệu nhỏ nhất có trong tay người đang giữ quân bài x, nghĩa là Find(9)=2 chứ không phải Find(9) = B. Để giải quyết sự khác biệt này ta hãy chọn phần tử có số hiệu nhỏ nhất trong tập các quân bài có trong tay một người làm phần tử đại diện của tập đó. Ta cũng đồng nhất phần tử đại diện với mã số của người giữ tập quân bài. Theo quy định này thì biểu thức Find(9)=2 có thể được hiểu theo một trong hai nghĩa tương đương như sau:
s         Người số 2 đang giữ quân bài 9.
s         Tập số 2 chứa phần tử 9.
Tổ chức hàm Find như trên có lợi là sau khi gọi i:=Find(x)j:=Find(y) ta xác định ngay được ai phải nộp bài cho ai. Nếu i < j thì j phải nộp bài cho i, ngược lại, nếu i > j thì i phải nộp bài cho j. Trường hợp i = j cho biết hai quân bài x y đang có trong tay một người, ta không phải làm gì.
Tóm lại ta đặt ra các nguyên tắc sau:
a) Lấy phần tử nhỏ nhất trong mỗi tập làm tên riêng cho tập đó.
b) Phần tử có giá trị nhỏ quản lí các phần tử có giá trị lớn hơn nó theo phương thức: mỗi phần tử trong một tập đều trỏ trực tiếp đến một phần tử nhỏ hơn nó và có trong tập đó. Phần tử nhỏ nhất trong tập trỏ tới chính nó.
Trong thí dụ trên ta có A = {3, 4, 7}, B = {2, 5, 9, 11}, x = 9 và y = 7.
Như vậy, tập A có phần tử đại diện là 3 và tập B có phần tử đại diện là 2.
Dữ liệu của tập A khi đó sẽ được tổ chức như sau:
A = {3 ® 3, 4 ® 3, 7 ® 3}. Như vậy 3 là phần tử đại diện của tập này, do đó ta không cần dùng biến A để biểu thị nó nữa mà có thể viết:
{3 ® 3, 4 ® 3, 7 ® 3} hoặc gọn hơn {3, 4, 7} ® 3.
Tương tự, dữ liệu của tập B sẽ có dạng:
{2 ® 2, 5 ® 2, 9 ® 2, 11 ® 2} hoặc gọn hơn {2, 5, 9, 11} ® 2.
Khi đó Find(9) = 2Find(7) = 3, và do đó, tập 3 phải được gộp vào tập 2. Phép Union(9,7) sẽ tạo ra tập sau đây:
{3 ® 2, 4 ® 3, 7 ® 3, 2 ® 2, 5 ® 2, 9 ® 2, 11 ® 2},
tức là ta thực hiện đúng một thao tác sửa 3 ® 3 thành 3 ® 2: để hợp hai tập ta chỉ việc đối sánh hai phần tử đại diện ij của chúng:
s         Nếu i > jthì cho tập i phụ thuộc vào tập j.
s         Nếu j > ithì cho tập j  phụ thuộc vào tập i.
s         Nếu i = j thì không làm gì.
Kĩ thuật nói trên được gọi là hợp các tập con rời nhau.
Ta dùng một mảng nguyên a thể hiện tất cả các tập. Khi đó hai tập A và B nói trên được thể hiện trong anhư sau:
a[3] = 3; a[4] = 3; a[7] = 3;
{tập A: phần tử đại diện là 3, các phần tử 3, 4 và 7 đều trỏ đến 3}
a[2] = 2; a[5] = 2; a[9] = 2; a[11] = 2;
{tập B: phần tử đại diện là 2, các phần tử 2, 5, 9 và 11 đều trỏ đến 2}
(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
(9)
(10)
(11)
(12)
(13)
(14)
(15)

2
(B)
3
(A)
3
(A)
2
(B)

3
(A)

2
(B)

2
(B)




Sau khi hợp nhất A với B ta được:
a[3] = 2; {chỗ sửa duy nhất}
a[4] = 3; a[7] = 3; a[2] = 2; a[5] = 2; a[9] = 2; a[11] = 2;
(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
(9)
(10)
(11)
(12)
(13)
(14)
(15)

2
2
3
2

3

2

2




Theo các nguyên tắc trên ta suy ra phần tử x là phần tử đại diện của tập khi và chỉ khi a[x] = x. Dựa vào đây ta tổ chức hàm Find(x): xác định phần tử đại diện của tập chứa x.
function Find(x: integer): integer;
begin
while (x <> a[x]) do x := a[x];
Find := x;
end;
Khi đó thủ tục Union được triển khai đơn giản như sau:
procedure Union(x, y: integer);
begin
x := Find(x);
y := Find(y);
if x = y then exit else
if x < y then a[y] := x
else {x > y} a[x] := y;
end;
Lúc bắt đầu chơi, mỗi người giữ một quân bài, ta khởi trị a[i]:=i cho mọi i = 1..n với ý nghĩa: tập có đúng một phần tử thì nó là đại diện của chính nó.
Để đếm số tập còn lại sau mỗi bước chơi ta có thể thực hiện theo hai cách.
Ta thấy, nếu Find(x)=Find(y) thì không xảy ra việc gộp bài vì xy cùng nằm trong một tập. Ngược lại, nếu Find(x)<>Find(y) thì do gộp bài nên số người chơi sẽ giảm đi 1 tức là số lượng tập giảm theo. Ta dùng một biến c đếm số lượng tập. Lúc đầu khởi trị c:=n (có n người chơi). Mỗi khi xảy ra điều kiện Find(x) <> Find(y) ta giảm c  1 đơn vị: dec(c).
Theo cách thứ hai ta viết hàm Dem để đếm số lượng tập sau khi kết thúc một lượt chơi. Ta có đặc tả sau đây:
số lượng tập = số lượng đại diện của tập.
Phần tử i là đại diện của một tập khi và chỉ khi a[i] = i.
Đặc tả trên cho ta:
function Dem: integer;
var d,i: integer;
begin
d := 0;
for i := 1 to n do
if a[i] = i then inc(d);
Dem := d;
end;
Dĩ nhiên trong bài này phương pháp thứ nhất sẽ hiệu quả hơn, tuy nhiên ta thực hiện cả hai phương pháp vì hàm Dem là một tiện ích trong loại hình tổ chức dữ liệu theo tiếp cận Find-Union.
Ta cũng sẽ cài đặt Union(x,y) dưới dạng hàm với giá trị ra là 1 nếu trước thời điểm hợp nhất x y thuộc về hai tập phân biệt và là 0 nếu trước đó xy đã thực sự có trong cùng một tập. Nói cách khác Union(x,y) cho biết phép hợp nhất có thực sự xảy ra (1) hay không (0).
Trong chương trình dưới đây tệp BAIGOP.INP chứa dữ liệu vào có cấu trúc như sau:
-          Dòng đầu tiên chứa hai số nguyên dương nm, trong đó n là số lượng quân bài, mlà số lần trọng tài phát sinh ra hai số ngẫu nhiên.
-          Tiếp đến là m dòng, mỗi dòng chứa hai số do trọng tài phát sinh. Các số được viết cách nhau bởi dấu cách.
Kĩ thuật trên có tên gọi là Find-Unionđóng vai trò quan trọng trong các thủ tục xử lí hợp các tập rời nhau. Trước khi xem chương trình chúng ta hãy thử làm một bài tập nhỏ sau đây:
Với mảng a được tổ chức theo kĩ thuật Find-Union dưới đây hãy cho biết có mấy tập con và hãy liệt kê từng tập một.
(1)
(2)
(3)
(4)
(5)
(6)
(7)
(8)
(9)
(10)
(11)
(12)
(13)
(14)
(15)
1
2
2
3
2
6
3
1
2
6
2
10
3
8
12
Đáp số: ba tập.
Tập với đại diện 1: {1, 8, 14}.
Tập với đại diện 2: {2, 3, 4, 5, 7, 9, 11, 13}.
Tập với đại diện 6: {6, 10, 12, 15}.
(*  Pascal  *)
(*-----------------------------------
               BAI GOP
------------------------------------*)
program BaiGop;
uses crt;
const
MN = 5000;
fn = 'BAIGOP.INP';
var
   a: array[1..MN] of integer;
   c,n,m: integer;
   {c: dem so tap
   n: so quan bai = so nguoi choi
   m: so lan trong tai sinh 2 so}
   f: text;
{----------------------------------
    Xac dinh tap chua phan tu x
-----------------------------------}
function Find(x: integer): integer;
begin
while (x <> a[x]) do x:= a[x];
Find := x;
end;
{-------------------------------------
  Hop cua tap chua x voi tap chua y.
  Union = 1: co hop nhat     
  Union = 0: khong hop nhat vi x va y
              thuoc cung 1 tap 
--------------------------------------}
function Union(x,y: integer): integer;
begin
Union := 0;
x := Find(x);
y := Find(y);
if x = y then exit
else if x < y then a[y] := x
else a[x] := y;
Union := 1;
end;
{-----------------------------------------------
  Dem so luong tap con roi nhau (so nguoi choi)
-----------------------------------------------}
function Dem: integer;
var d,i: integer;
begin
d := 0;
for i := 1 to n do
if a[i] = i then inc(d);
Dem:= d;
end;
procedure BaiGop;
var i,j,k,x,y: integer;
begin
assign(f,fn); reset(f);
readln(f,n,m);
{n – so quan bai; m – so lan chon x,y}
c := n; {c: so nguoi choi}
if (n < 1) or (n > MN) then exit;
for i := 1 to n do a[i] := i;
for i := 1 to m do
    begin
       readln(f,x,y);
       c := c-Union(x,y);
    end;
writeln(c);
close(f);
end;
BEGIN
BaiGop;
END.

Không có nhận xét nào:

Đăng nhận xét