email: lightnumberss@gmail.com

29/7/13

[Vui vui] Đảo ngược xâu chỉ với một biến với ngôn ngữ Pascal (Phần 1)

Bạn vui lòng hiểu rằng "một biến" ở đây có nghĩa là trong khai báo var của toàn chương trình (kể cả khai báo tham biến, tham trị của chương trình con) chỉ có một biến duy nhất được khai báo. Không được phép khai báo record để lấy các trường làm các biến riêng biệt. Nếu thế thì quá dễ dàng rồi O_o


Suy nghĩ đầu tiên của mình khi nhìn thấy vấn đề này là pointer. Mình đã thử khai báo "s: pointer;" nhưng lại gặp vấn đề về ép kiểu để nhập chuỗi vào. 

Tới đây mình nhận thấy rằng vấn đề quan trọng nhất chính là làm sao đọc được chuỗi vào. Vì thế "biến duy nhất" của ta chỉ có thể là "s: shortString;" hoặc "s: ^shortString". Và mình đã chọn cách thứ hai, bởi nếu khai báo theo cách thứ nhất thì việc duy nhất có thể làm được chỉ là nhập xâu vào.

Cần phải có một chút gợi nhớ về cái khai báo "s: ^shortString" này. Ta đã biết rằng, khi khai báo như trên thì s ở đây chỉ là một "con số" đại diện cho một byte trong bộ nhớ máy tính, là vị trí của shortString tương ứng với s  trong bộ nhớ. Nói cách khác, s chỉ là địa chỉ nhà của một shortString thật sự mà thôi, không phải là chính shortString đó.

Như vậy ý tưởng của chúng ta sẽ là dùng chính biến địa chỉ s đó để in ra các kí tự. 
Nhắc lại rằng, mỗi shortString có độ dài 256 byte, và byte đầu tiên (kí hiệu 0 ở trên) sẽ lưu độ dài thực của xâu đang chứa (vậy thì nó phải có giá trị là #4 mới đúng, ở đây dùng '0' cho dễ thấy).

Bởi vậy, ý tưởng là mình sẽ tìm cách cho s chạy tới vị trí chữ D (tức là vị trí cuối cùng của xâu) rồi từ đó chạy ngược về vị trí A, lần lượt in ra các giá trị hiện tại sẽ được xâu đảo ngược. Thế nhưng việc này không hề dễ dàng bởi không thể lắp biến s vào một câu lệnh lặp for để lợi dụng "hai biến ẩn" của nó được (nói nhỏ: "hai biến ẩn" của for là cách nói của mình, ám chỉ lNumber1 và lNumber2 trong câu lệnh "for i := lNumber1 to lNumber2 do". Khi thực hiện lệnh này, máy tính sẽ xây dựng hai ô nhớ lưu hai giá trị lNumber1 và lNumber2 riêng biệt với i, vì thế câu lệnh "for i := i to i+5 do" là hoàn toàn hợp lệ).

Nhưng với lệnh lặp while...do hay repeat...until, điều đó ("biến ẩn") không còn đúng nữa. Biến đếm sẽ mang giá trị của chính nó, chính ô nhớ đó, vì vậy nó sẽ thay đổi. Trớ trêu thay, biến con trỏ không được phép cho vào lệnh lặp ("for s := address1 to address2 do" là một câu lệnh sai). Ta buộc phải tìm cách sử dụng lệnh lặp while...do hoặc repeat...until.

Ở đây mình đã chọn while...do mà không chọn repeat...until bởi còn xét tới người dùng nhập xâu rỗng, repeat...until sẽ gây khó khăn cho xử lí trường hợp này. Mà giờ...while cái gì do ? Phải có một điều kiện để dừng việc "nhảy tới vị trí chữ D". Thế là nghĩ ngay tới kĩ thuật "lính canh": Đặt cho vị trí sau chữ D một giá trị không thể nhập vào được, thí dụ như mình chọn #5. Vì sau này cũng sẽ phải đi ngược lại về ban đầu để in xâu ngược, mình cũng đặt luôn vị trí 0 (tức là s^[0] theo ngôn ngữ Pascal) là #5 luôn. Mọi sự chuẩn bị đã sẵn sàng.

Chú ý rằng bây giờ gọi s^ sẽ không còn trả về 'ABCD' nữa mà sẽ trả về 'ABCD' cùng với kí tự #5 cuối cùng (bởi độ dài xâu đã bị đặt lại là 5 ở phần tử s^[0] đầu tiên rồi, nhưng điều đó không quan trọng).
Hình như còn điều gì đó chưa ổn. Làm cách nào để s có thể dịch từ từ từng byte như ý ta muốn ? Ta có phép tính s := s+1, nhưng điều đó chỉ khiến cho s dịch phải một khoảng bằng kích thước của s (256 byte thay vì 1 byte như ta muốn). Mọi cách hòng thay đổi giá trị của s đều khiến cho nó nhảy tới hoặc nhảy lui một bội của 256. Tới đây gần như vô vọng.

Nhưng không. Vẫn còn một con đường để thoát khỏi bế tắc này, đó chính là kí tự "@". Trong ngôn ngữ Pascal, "@" là toán tử trả về địa chỉ của một biến, ví dụ "@i" sẽ trả về địa chỉ ô nhớ biến i trong bộ nhớ. Đó chính là chìa khóa của chúng ta. Bằng cách đặt "s := @s^[1]", ta đã "đặt địa chỉ s bằng địa chỉ của s^[1], nghĩa là s giờ đang nằm ở vị trí này:

Tới đây thì hình như mọi chuyện đã quá rõ ràng rồi. Bằng kĩ thuật đặt "s := @s^[1]", ta nhảy lần lượt đến khi gặp s^[1] (ô tiếp theo) bằng #5 thì dừng. Đến đây, một cuộc phiêu lưu khác lại diễn ra.

(Chờ hồi sau)

20/7/13

[Lazarus Pascal] Về Asynchronous Calls

Lược dịch lại từ Asynchronous_Calls

Phát biểu vấn đề 


Khi đang xử lí một Event, bạn cần phải làm một vài công việc khác, nhưng lại không thể làm ngay. Thí dụ: bạn cần free một object, nhưng nó lại tham chiếu tới một phần khác trong parent của mình, dẫn tới công việc "free một object" bị lỗi không thể thực hiện. 

Giải pháp 

Sử dụng Application.QueueAsyncCall: 

TDataEvent = procedure (Data: PtrInt) of object; 
procedure QueueAsyncCall(AMethod: TDataEvent; Data: PtrInt); 


Lệnh này sẽ đặt một method cho trước cùng với danh sách tham biến tương ứng cho trước vào một "hàng đợi" để thực thi trong main event loop, sau khi tất cả các sự kiện khác đã được xử lí. Trong vi dụ trên đây, tham chiếu của vật thể mà object chỉ tới đã tiêu mất, vì parent đã hoàn tất thực thi, vì thế bạn hoàn toàn thoải mái free object mà không đụng chạm ai cả. 
Lưu ý rằng đây là phiên bản tổng quát hơn ReleaseComponent, và ReleaseComponent gọi method này. 

Ví dụ 


Chương trình dưới đây sẽ giới thiệu về công dụng của QueueAsyncCall. Nếu bạn nhấn CallButton, 'Click 1', 'Click 2' và 'Async 1' sẽ được thêm vào LogListBox. Để ý rằng lời gọi Async chỉ được thực thi một khi sự kiện CallButtonClick kết thúc. 

3/7/13

Yuuki 100% !!!

Yosh!

Bài kệ lúc bắt đầu hành thiền và khi xả thiền

Lúc bắt đầu hành thiền:
"Xin Phật độ cho con
Luôn nhớ và hiểu rằng
Thân này không phải ta
Tâm này không phải ta
Chẳng có gì là ta
Trong từng hơi thở vào
Trong từng hơi thở ra
Trọn niềm tôn kính Phật
Nam mô Bổn sư Thích ca Mâu Ni Phật"
Lúc xả thiền:
"Tam Bảo gia hộ cho con
Lúc thức cũng như lúc ngủ
Ban ngày cũng như ban đêm
Luôn nhớ thân này vô thường
Khi đi hoặc là khi đứng
Khi ngồi hoặc là khi nằm
Lúc làm việc hay nghỉ ngơi
Luôn nhớ thân này là vô thường
Khi nghe cũng như khi nói
Đông người hay ở một mình
Xem phim hay là đọc sách
Luôn nhớ thân này vô thường
Lúc ăn cơm hay uống nước
Khi tắm rửa hay vệ sinh
Đắp y hay mang giày dép
Luôn nhớ thân này vô thường
Những khi tâm con tĩnh giác
Càng nhớ thân này vô thường
Nguyện cho chúng sinh khắp chốn
Luôn biết thân này vô thường
Nam mô Bổn sư Thích ca Mâu Ni Phật
Nam mô Bổn sư Thích ca Mâu Ni Phật
Nam mô Bổn sư Thích ca Mâu Ni Phật" 

28/5/13

HellBound Hackers: Basic Web Hacking (1-5)

Hôm nay mới tình cờ vào được trang HellBound Hackers (https://www.hellboundhackers.org), thấy khá hấp dẫn, hi vọng sẽ học được nhiều điều từ đây.
Tôi bắt đầu với 5 màn đầu tiên của Basic Web Hacking (◎_◎).

Basic Web Hacking 1: 
Biết được: Không có gì ngoài một câu đố vui.
Màn này là căn bản nhất. Tôi đã thử xem nguồn trang để tìm một gợi ý nào đó (vì trên trang index chẳng có gì ngoài một cái ô trống để nhập password và một cái nút bấm) và nhận thấy một dòng comment (lưu ý rằng dòng comment này sau này có thể thay đổi):

<!-- When you pet me, I purr. -->

Đây có vẻ gì là một gợi ý không ?
Và tôi nghĩ ngay tới một con hổ, hoặc một con vật gần với con hổ. Sau đó tôi gõ vào ô trống từ mà tôi đang nghĩ trong đầu. Great Success !

Basic Web Hacking 2:
Biết được: Thẻ IFRAME.
Màn này cũng không có gì khó. Mấu chốt của màn này là giới thiệu về IFRAME - nôm na là một thẻ của HTML dùng để chèn một "trang" khác vào trang hiện tại. Theo yêu cầu cần tìm url của IFRAME, tôi đã dùng lại kĩ thuật y như Basic Web Hacking 1 để lấy url, sau đó dán-click-Great Success !

Basic Web Hacking 3:
Biết được: User Agent và plugin User Agent Switcher.
Màn này khiến cho tôi hơi sốc bởi chưa từng biết tới User Agent là gì. Ngay trước mặt tôi là hai dòng ngắn gọn, khó hiểu:

Now, Drake learned how to make http user agents with php. 
Wrong user_agent, bwh3_user_agent wasn't found

Tôi tìm hiểu trên mạng chỉ để biết được user_agent là gì. "User agent" chỉ một "thứ" giúp bạn duyệt web bằng nhiều loại trình duyệt khác nhau (trình độ giải thích của tôi hạn chế lắm hu hu hu), mỗi loại được gọi là một agent. Và cái mission 3 này yêu cầu một bwh3_user_agent. Tôi cài vào Chrome cái plugin User Agent Switcher.
Tôi lục tìm trong danh sách agent cung cấp sẵn mà không có cái nào tương tự cả. Sau tôi mới thấy nút Add...Great Success !

Basic Web Hacking 4:
Biết được: Không có gì.
Thực tình thì tôi đã chơi ăn gian trong mission này @_@.
Đầu tiên tôi thử dán "htpasswd.php" vào sau url, thay cho "index.php" xem sao...Great...damn it !
Sau đó tôi lục tìm trong mã nguồn xem có thông tin gì không (vì đó là tất cả những gì tôi có lúc này). Không một gợi ý về user_agent, có lẽ không dùng tới.
Cuối cùng đành phải chịu thôi, tôi đi tìm một gợi ý trên mạng cho mission này. Và câu trả lời là hãy thay "basic4" thành "basic" + <một con số khác>.
Ơ thì ra cái này hỏi đố (>.<).

Basic Web Hacking 5:
Biết được: "Ứng dụng" wildcard.
Vâng, hoàn cảnh là tôi cần tìm một địa chỉ email và password để đăng nhập vào hệ thống của Asterix-Protect. Vì chưa có ý tưởng gì cho mission này, tôi làm công việc "muôn thuở": xem mã nguồn. Và có một dòng đáng chú ý (y như mission 1):

<!--attention admin: * is a wildcard -->

Như vậy có cách để làm cho tất cả các chuỗi đều như nhau: "hsdfah" cũng như "fsdhafhew" cũng như chuỗi X nào đó thoả mãn yêu cầu, đó chính là dùng chỉ duy nhất một kí tự "*". Vậy rồi, tôi gõ vào ô như sau (yêu cầu địa chỉ email + ":" + password):

*@*:*

Great Success !

10 kinh nghiệm vàng trong cuộc sống (st)

10 kinh nghiệm vàng trong cuộc sống

6/1/13

[Vui vui] So sánh

Một bài tập vui:
"Viết chương trình nhập vào hai số nguyên. In ra màn hình số lớn hơn trong hai số vừa nhập. Chú ý không được phép sử dụng lệnh IF và các loại thư viện."
.
.
.
.
.
Bạn làm được chưa ? (=`ω´=)
.
.
.
.
.
Bạn có code nào chưa ? Bao nhiêu dòng ? ___ψ(‥ )
.
.
.
.
.
Bạn đã thực sự hài lòng với nó chưa ? Thử cải tiến xem ? ☆*:.。. o(≧▽≦)o .。.:*☆
.
.
.
.
.
Hãy thử so sánh với code này !

Have fun ^^




Omochikaeri~!

5/12/12

Bài toán Josephus, ai sẽ là người cuối cùng ?

Có một bài toán đặt ra như sau: "Cho N người đứng thành vòng tròn và số M (ở đây cho dễ ta sẽ giả sử M < N). Bắt đầu từ người số 1, anh ta sẽ đếm 1, người bên phải anh ta đếm 2, ... cho tới người đếm M sẽ tự động bước ra khỏi vòng tròn đó và người bên phải anh ta tiếp tục đếm lại 1 ... Cứ thế cho tới khi vòng không còn người nào. Câu hỏi đặt ra là ai là người ra khỏi vòng cuối cùng ?"

Bằng một ít suy nghĩ có lẽ ta sẽ tìm ra ngay các trả lời cho câu hỏi này. Chẳng hạn, bạn có thể "giả lập" trò chơi trên với một dãy số nguyên từ 1 đến N và số M, thực hiện bỏ từng số cho đến khi nào dãy chỉ còn một số và đó chính là kết quả. Như vậy bạn phải chạy N "vòng" (vì mỗi "vòng" bỏ một số) và mỗi "vòng" bạn phải đếm qua M người. Như vậy với cách giả lập trò chơi bạn mất M x N thao tác đếm.

Thực chất lí do mà mình viết bài này vì ... thực sự quá (?) sung sướng khi bắt gặp trên VNOI một cách giải mới có thể đưa số lần tính toán giảm xuống rất nhiều (MlogN). Ta thử ví dụ một trò chơi với N = 8 và M = 5 nhé:

Đầu tiên ta có:     
1 2 3 4 5 6 7 8

Với những số không bị bỏ đi và nằm phía trước số bị bỏ, đem chúng cho vào phía sau (thì coi như trò chơi mới với dãy gồm N - 1 người)

6 7 8 9 10 11 12

Tiếp tục:
11 12 13 14 15 16
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
...

Bạn sẽ chú ý tới 3 dòng cuối cùng (và một số dòng ở dưới đó). Hãy suy nghĩ một chút sẽ biết tại sao lại làm như vậy. Chỉ là hợp thức hóa vấn đề phát sinh này thôi, không có gì quan trọng cả. Điều quan trọng ở đây là, trong dãy số mới, các số bị bỏ đi bao giờ cũng là k x M và số bị bỏ đi cuối cùng chính là M x N. Ta sẽ tìm hiểu số mang thứ tự M x N trong dãy mới kia sẽ tương ứng với người mang thứ tự bao nhiêu ở dãy 1..N ban đầu, và đó sẽ là kết quả.

Một nhận xét, các số M x k luôn bị bỏ đi,và M x k + r (0 < r < M) sau một lần chuyển sẽ chuyển lên vị trí N + (M - 1) x k + r. Cho phép mình giải thích cái công thức kia một chút. Tại sao số M x k + r sẽ trở thành số N + (M - 1) x k + r sau một phép "chuyển ra sau" ? Giả sử bạn đang xóa số M x k, thì đương nhiên phía trước đó nhất định phải còn lại M - 1 số (xem ví dụ), bạn chuyển M - 1 số đó ra sau (đương nhiên là sau số N), như vậy bạn bỏ qua bao nhiêu lần M thì cũng chuyển bấy nhiêu lần M - 1 ra sau, đó là lí do tại sao ta có cái công thức đấy.

Như vậy ta có vị trí cuối cùng là M x N và công thức chuyển vị trí (M x k + r) thành (N + (M - 1) x k + r), ta sẽ tìm cách chuyển về cho tới khi vị trí tính được nhỏ hơn N là xong.

Cách chuyển như thế nào ? Giả sử vị trí hiện tại là P, ta có:
P = N + (M - 1) x k + r
Do đó:
P - n - 1 = (M - 1) x k + (r - 1)
Suy ra:
k = [(P - n - 1) / (M - 1)] và P + k - N = M x k + r
Do đó vị trí trước sẽ là:
M x k + r = Ptrước = Psau + [(Psau - n - 1)/(M - 1)] - N
Tính vị trí trước như thế cho tới khi nào nó nhỏ hơn hoặc bằng N thì dừng. Đó chính là vị trí ban đầu của M x N.

Code của mình: 
var
        p, n, q, nn, m, mm: qword;
BEGIN
        read(n, m); p := m * n; nn := n + 1; mm := m - 1;
        while p > n do p := p + (p - nn) div mm - n;
        write(p);
END.
Một vài chú ý nhỏ: 
+ Code trên là code mình đã tối ưu hết cỡ (tránh tính lại n + 1, m - 1)
+ Trong một biểu thức tính, nếu mà các hạng tử khác kiểu (longint và qword, ...), thì máy tính sẽ phải chuyển về cùng kiểu rồi mới tính, do đó chậm hơn để cùng kiểu luôn.
+ Mình viết bài này nhằm cung cấp tri thức và chia sẻ kinh nghiệm, không phải để chơi. Do đó, nếu bạn vào chỉ để copy code gửi lên máy chấm để lấy điểm thì các bạn hiểu, đó không phải là công sức của các bạn, các bạn không xứng đáng nhận lấy con điểm 100 đó. Thân ái.

13/11/12

Types of programming contest problems


There are 16 types of programming contest problems (according to USACO)
  • Dynamic Programming
  • Greedy
  • Complete Search
  • Flood Fill
  • Shortest Path
  • Recursive Search Techniques
  • Minimum Spanning Tree
  • Knapsack
  • Computational Geometry
  • Network Flow
  • Eulerian Path
  • Two-Dimensional Convex Hull
  • BigNums
  • Heuristic Search
  • Approximate Search
  • Ad Hoc Problems