Trong toán học, quy nạp là một phương pháp đơn giản nhưng hiệu quả để chứng minh các bài toán. Ở bài viết này tôi xin đưa ra một ứng dụng nhỏ của nó trong việc giải các bài toán tin học:

I. Thuật toán quy nạp: 

Giả sử có bài toán F cần chứng minh đúng với mọi \(n \in N\). Ta chứng minh bài toán đúng bằng cách quy nạp, cần tiến hành các bước sau: 

  • n = 1: mệnh đề cần chứng minh đúng. 

  • Giả sử n = k: mệnh đề cần chứng minh đúng. 

  • n = k + 1: ta cần chứng nó cũng đúng. Vậy theo nguyên lý quy nạp bài toán đúng với mọi N. 

Trong tin học, thuật toán này cũng được áp dụng. Tuy thuật toán đơn giản nhưng nó lại được áp dụng một cách rất linh động và khéo léo trong các bài toán tin. 


II. Phát biểu bài toán tổng quát giải bằng quy nạp: 

Thông thường bài toán giải bằng quy nạp không phải là một bài toán tối ưu hoá. Nó chỉ đơn giản là bài toán cần chỉ ra cách biến đổi theo quy luật cho trước để thu được kết quả mong đợi. Và bài toán đó thường được phát biểu như sau:Cho N đối tượng và một số thao tác biến đổi. Yêu cầu sử dụng các thao tác biến đổi để thu được kết mong đợi. 
Cách làm thông thường: 

  • Nếu n = 0; 1: ta luôn có cách biến đổi đúng. 

  • Nếu có n > 1 mà ta luôn chỉ ra một cách biến đổi sao cho giản bớt được số đối tượng mà điều kiện bài toán vẫn không thay đổi. 

  • Như vậy vì số đối tượng trong một bài toán tin học luôn là hữu hạn nên sau một số hữu hạn bước thì số lượng đối tương bằng 1 hoặc 0. Suy ra bài toán được giải quyết một cách hoàn toàn.

 

III. Các ví dụ cụ thể: 


P1.  

     Cho N vecto v1, v2, …, vn độ dài nhỏ hơn L cho trước. Cần đặt trước các vecto một dấu cộng hoặc trừ sao cho vecto tổng thu được có độ dài nhỏ hơn căn 2 nhân L.

Input: Segment.In 

  •  Dòng đầu ghi số N ( 1 < N ≤ 10,000) và L (0 < L ≤ 15,000.00) 

  •  N dòng tiếp theo mỗi dòng ghi một cặp số xi, yi là toạ độ của vecto thứ i. (|xi|, |yi| ≤ 10000). 

Ouput: Segment.out 

  •  Dòng thứ nhất ghi YES nó có thể đặt các dấu cộng trừ thoả mãn yêu cầu bài toán, NO trong trường hợp ngược lại. 

  •  Nếu là YES dòng thứ hai ghi N kí tự cộng hoặc trừ cần đặt trước các vecto sao cho tổng vecto nhỏ hơn \(\sqrt{2} * L\)

Thuật giải: 

  •  n = 1 bài toán đã đúng. 

  •  n = 2 có trường hợp xảy ra với hai vecto: 

    • góc giữa hai vecto nhỏ hơn 90 độ, lấy vecto 1 trừ cho vecto 2 lúc đó ta thu được 1 vecto co độ dài nhỏ hơn \(\sqrt{2} * L\).

    • góc giữa hai vecto lớn hơn hoặc bài một 90 độ, lấy vecto 1 cộng cho vecto 2 lúc đó ta thu được 1 vecto có độ dài nhỏ hơn \(\sqrt{2} * L\)

  •  n ≥ 3 suy ra luôn có 2 vecto mà góc giữa hai phương của vecto nhỏ hơn hoặc 60 bằng độ có hai trường xảy ra: 

    • góc giữa hai vecto đó nhỏ hơn hoặc bằng 60 độ, trừ vecto 1 cho vecto 2 thay hai vecto bằng vecto mới nhận được. 

    • góc giữa hai vecto đó lớn hơn hoặc bằng 120 độ, cộng vecto 1 cho vecto 2 thay hai vecto bằng vecto mới nhận được. Lưu ý sau này nếu trừ vecto mới nhận được phải trừ (đổi dấu) toàn vecto bộ hình thành lên nó, vecto mới nhận được có độ dài nhỏ hơn L, số đoạn thẳng giảm 1. 

Như vậy đây là bài toán luôn có lời giải, độ phức tạp O(N^2) nếu xử lý khéo có thể giản xuống O(n*log(n)). Chương trình mô tả thuật giải: 
 

Procedure Xuly (n: integer //* số đối tượng *//);
  Begin
       If n <= 1 then exit else
       If n = 2 then 
          Begin
               If goc (v1, v2) <= 90 độ Then đổi dấu (v2);
               //* đổi dấu tức là đổi dấu toàn bộ vecto thành phần *//
               Thay hai vecto bằng 1 vecto (v1 + v2);
               //* vecto mới gồm các thành phần của v1 và v2 *//
               //* lúc này v2 nếu cần thiết đã đổi dấu rồi *//
               Exit;
          End 
            else
              Begin
                  lấy 3 vecto bất kỳ;
                  chọn ra được 2 vecto v1, v2 sao cho góc nhỏ giữa phương 2 vec to nhỏ 60;
                  if goc (v1, v2) <= 60 then đổi dấu (v2);
                  Thay hai vecto này bằng (v1 + v2);
              End;
  End;

P2: Utopia (IOI 2002) 


Đất nước Utopia tươi đẹp bị chiến tranh tàn phá. Khi chiến tranh tạm ngừng, đất nước bị chia cắt thành bốn miền bởi một đường kinh tuyến (đường theo hướng Bắc-Nam) và một đường vĩ tuyến (đường theo hướng Đông-Tây). Giao điểm của hai đường này xem như điểm (0, 0). Tất cả các miền này đều muốn mang tên Utopia, theo thời gian các miền này được gọi là Utopia 1 (phía Đông Bắc), Utopia 2 (phía Tây Bắc), Utopia 3 (phía Tây Nam), Utopia 4 (phía Đông Nam). Một điểm trong bất kỳ miền nào cũng được xác định bởi khoảng cách theo hướng Đông và khoảng cách theo hướng Bắc của nó so với điểm (0, 0). Bộ hai khoảng cách này được gọi là toạ độ của điểm. Các thành phần của toạ độ có thể là số âm; do đó một điểm miền Utopia 2 có toạ độ (âm, dương), một điểm miền Utopia 3 có toạ độ (âm, âm), một điểm miền Utopia 4 có toạ độ (dương, âm), một điểm miền Utopia 1 có toạ độ (dương, dương) (Giống như các góc phần tư trong hệ toạ độ đề các). 
Một vấn đề là các công dân không được phép đi qua biên giới giữa các miền. May thay, một số thí sinh tham dự IOI của Utopia sáng chế ra một loại máy an toàn để vận chuyển từ xa. Máy này cần các số mã, mỗi số mã chỉ có thể được dùng một lần. Bây giờ, nhóm thí sinh tham dự và bạn phải hướng dẫn máy vận chuyển từ xa xuất phát từ vị trí ban đầu (0, 0) đến các miền của Utopia theo một trình tự cho trước. Khi bạn nhận được một dãy N số hiệu miền mà theo trình tự đó máy vận chuyển từ xa phải hạ cánh, với mỗi miền, bạn không cần phải quan tâm đến việc hạ cánh tại điểm nào của miền đó miễn là điểm đó thuộc miền yêu cầu. Người ta có thể yêu cầu máy hạ cánh liên tiếp hai lần hay nhiều lần hơn ở cùng một miền. Sau khi rời khỏi điểm ban đầu (0, 0), bạn không được hạ cánh trên các đường biên giới. 
Bạn sẽ nhận được input là một dãy 2N số mã và phải viết chúng thành một dãy gồm N cặp mã, sau khi đặt một dấu + hoặc một dấu - thích hợp trước mỗi số. Nếu hiện tại bạn ở điểm (x,y) và sử dụng cặp mã số (+u,-v), bạn sẽ được chuyển tới điểm (x+u,y-v). Bạn có 2N số và bạn có thể sử dụng các số này theo thứ tự bạn muốn, mỗi số kèm theo một dấu + hoặc một dấu -. 
Giả sử bạn có các số mã 7, 5, 6, 1, 3, 2, 4, 8 và phải hướng dẫn máy vận chuyển từ xa lần lượt đến các miền thuộc dãy miền 4, 1, 2, 1. Dãy các cặp mã (+7, -1), (-5, +2), (-4, +3), (+8, +6) đáp ứng yêu cầu này vì nó đưa bạn từ (0,0) lần lượt đến các vị trí (7, -1), (2, 1), (-2, 4) và (6, 10). Những điểm này nằm tương ứng ở Utopia 4, Utopia 1, Utopia 2 và Utopia 1.

Nhiệm vụ:

Cho 2N số mã khác nhau và một dãy N số hiệu miền là dãy các miền mà máy vận chuyển từ xa phải lần lượt hạ cánh. Hãy xây dựng một dãy các cặp mã từ các số đã cho để hướng dẫn máy vận chuyển từ xa lần lượt đi đến các miền thuộc dãy đã cho. 


Input 

Chương trình của bạn phải đọc từ input chuẩn. Dòng đầu gồm một số nguyên dương N (1 ≤ N ≤ 10000). Dòng thứ hai gồm 2N số mã nguyên khác nhau, tất cả đều là các số nguyên dương không lớn hơn 100000. Dòng cuối cùng gồm một dãy N số hiệu miền, mỗi một trong các số đó là 1, 2, 3 hoặc 4. Hai số liên tiếp trên một dòng cách nhau đúng một dấu trống. 


Output 

Chương trình của bạn phải viết ra output chuẩn. Ouput gồm N dòng, mỗi dòng chứa một cặp số mã mà trước mỗi số có dấu + hoặc dấu -. Đó là các cặp mã sẽ hướng dẫn cho máy vận chuyển đến dãy miền đã cho. Chú ý rằng không được có dấu trống sau dấu + hoặc dấu - nhưng phải có một dấu trống sau số mã thứ nhất.Nếu có nhiều lời giải, chương trình của bạn có thể đưa ra một lời giải bất kỳ trong số đó. Nếu không có lời giải, chương trình của bạn phải đưa ra một số 0 duy nhất. 

Thuật giải: 

Đây cũng là bài toàn luôn giải được để giải bài toán trên ta cần giải bài toán nhỏ sau:Cho một gồm N số nguyên dương khác nhau a1 < a2 < … < an. Cần sắp xếp lại và thêm các dấu vào các số ai sao cho tổng các số từ 1 đến vị trí i là dương hoặc âm biết trước: 


Ví dụ: 

Dãy 3 số: 1 2 3 
Dấu cần xây dựng: + - + 
Một cách thêm dấu và sắp xếp: +1 -2 + 3 


Bổ đề: 

Nếu dãy có dãy {ai} dương tăng và một chuỗi dấu cần biến đổi thì ta luôn có cách thêm dấu và thay đổi vị trí sao cho tổng cách số từ 1 đến i mang đấu của chuỗi dấu biết trước. 


Chứng minh: 

  • số an mang dấu cuối cùng của chuỗi trên, các số còn lại đan dấu. (điều kiện về dấu để luôn xây được dãy) Ví dụ: {ai} = {1, 5, 10}; S = ‘++-‘; suy ra dãy {ai} được đánh dấu như sau: -1, +5, -10; 

  • nhận xét rằng: tổng của n số trên cuối cùng sẽ mang dấu của S[n] 

  • với n = 1 ta nhận được dãy cần tìm. 

  • Giả sử n = k ta xây dựng được chuỗi như trên. 

  • n = k + 1 chia hai trường hợp: 

    • s[n-1] cùng dấu s[n]: đặt a[1] vào vị trí cuối cùng và lấy phần tử a[1] và s[n] ra khỏi dãy, nhưng còn là (n - 1) số và (n-1) dấu và dấu cuối cùng với số cuối, dãy đan dấu (vẫn thoả mãn điều kiện về dấu) 

    • s[n - 1] và s[n] khác dấu: đặt a[n] vào vị trí cuối cùng và lấy phần tử a[n] và s[n] ra khỏi dãy, nhưng còn là (n - 1) số và (n-1) dấu và dấu cuối cùng với số cuối, dãy đan dấu. Theo giả thiết quy nạp thì dãy còn lại luôn xây dựng được. 


Chương trình: 
 

Procedure Xuly (Var a, dau, q: array [1…10000] of integer; n, cd, ct : integer);
//*ta đang xử lý dãy các số từ a[cd] đến a[ct] có dấu từ dấu[1] đến dấu[n] *//
Begin
   If n <= 1 then exit;
   If dau[n - 1] = dau[n] then
       Begin
          q[n] := a[cd];
          Xuly (a, dau, q, n - 1, cd + 1, ct);
       End 
         else
            Begin
               q[n] := a[ct];
               Xuly (a, dau, q, n - 1, cd, ct - 1);
            End;
End;
//* mảng q thu được là kết quả cần tìm*// 


Ở bài toán trên các dấu tọa độ x, y là đã xác định: Chia dãy 2N phần tử khác nhau thành hai gồm N phần tử làm hoành độ và tung độ, sau đó sắp tăng. Tiến hành thuật giải trên ta thu được hai dãy toạ độ thoả mãn điều kiện về dấu, đó cũng chính là dãy toạ độ cần tìm.

P3: (Bài tập tự giải) 2N point (POI) 


Cho 2N diểm trên mặt phẳng gồm N diểm màu xanh và N điểm màu trắng sao cho không có ba điểm nào thẳng hàng. Hãy tìm N đoạn thẳng mà mỗi đoạn thẳng nối giữa một điểm màu xanh và một điểm màu trắng và các doạn nay không cắt nhau. 


Input: 2Npoint.Int 

  •  Dòng dầu ghi số N. (1 ≤ N ≤ 5000) 

  •  N dòng tiếp theo mỗi dòng ghi toạ độ xi, yi của điểm màu xanh \((|x_i|, |y_i| ≤ 10,000)\).

  •  N dòng tiếp theo mỗi dòng ghi toạ độ xi, yi của điểm màu trắng \((|x_i|, |y_i| ≤ 10,000)\)

Output: 2Npoint.Out 

  •  Gồm N dòng mỗi dòng ghi hai số a, b để chỉ điểm màu xanh thứ a, nối với điểm màu trắng thứ b. 

Mình có vài góp ý về cách trình bày:

  • Với code, bạn nên dùng chức năng chèn code (nút thứ 6 từ phải sang)
  • Thay vì gạch đầu dòng, dùng bullet (nút thứ 6 từ trái sang)
  • Các mục 1, 2, 3 nên dùng Heading 2. Để bold nhìn nó lẫn với mấy cái như "Input", "Output"...

đô chép ở mô đây đô :v 

Trả lời qwerty22121998
  Hiện bài gốc

Bài này là từ VNOI cũ :D