| # | Bài tập | Điểm | Thời gian: | Giới hạn bộ nhớ |
|---|---|---|---|---|
| 1 | IOI 2012 - City | 100 (p) | 5.0s | 256M |
| 2 | IOI 2012 - Supper | 100 (p) | 5.0s | 256M |
| 3 | IOI 2012 - Tournament | 100 (p) | 5.0s | 256M |
Giống nhiều nhà khoa học và nghệ sĩ Ý cùng thời, Leonardo rất quan tâm đến quy hoạch và thiết kế đô thị. Ông muốn xây dựng một thành phố lý tưởng: tiện nghi, rộng rãi và sử dụng tài nguyên hợp lý, tránh sự chật hẹp, tù túng của các thành phố thời Trung Cổ.
Thành phố gồm \(N\) khối đặt trên một lưới ô vuông vô hạn. Mỗi ô được xác định bởi cặp tọa độ (hàng, cột). Các ô kề với \((i,j)\) là \((i-1,j)\), \((i+1,j)\), \((i,j-1)\) và \((i,j+1)\). Mỗi khối phủ đúng một ô và chỉ có thể đặt tại \((i,j)\) khi \(1 \le i,j \le 2^{31}-2\). Tọa độ của một ô cũng được dùng để chỉ khối nằm trên ô đó. Hai khối kề nhau nếu nằm trên hai ô kề nhau.
Trong một thành phố lý tưởng, các khối liên thông và không có lỗ hổng bên trong đường biên. Cụ thể, phải thỏa mãn cả hai điều kiện:
Khi đi trong thành phố, một bước nhảy là di chuyển từ một khối sang khối kề nó; không được đi qua ô trống. Gọi \(v_0,v_1,\ldots,v_{N-1}\) là tọa độ các khối. Khoảng cách \(d(v_i,v_j)\) giữa hai khối khác nhau là số bước nhảy ít nhất để đi từ khối này đến khối kia.
Cho một thành phố lý tưởng, hãy tính tổng khoảng cách giữa mọi cặp khối \(v_i,v_j\) với \(i<j\):
Cài đặt chương trình con DistanceSum(N, X, Y), trong đó hai mảng \(X,Y\) đều có \(N\) phần tử; khối \(i\) ở tọa độ \((X[i],Y[i])\) với \(0 \le i \le N-1\). Vì kết quả có thể vượt quá khả năng biểu diễn bằng số nguyên \(32\) bit, hãy trả về tổng theo mô đun \(1\,000\,000\,000\) (một tỉ).
Nộp đúng một tệp city.c, city.cpp hoặc city.pas, cài đặt chương trình con với chữ ký sau.
C/C++
int DistanceSum(int N, int *X, int *Y);
Pascal
function DistanceSum(N : LongInt; var X, Y : array of LongInt) : LongInt;
Chương trình con phải hoạt động như đã mô tả. Bạn có thể cài đặt thêm các chương trình con dùng nội bộ. Bài nộp không được giao tiếp dưới bất kỳ hình thức nào với đầu vào/đầu ra chuẩn hoặc với bất kỳ tệp nào khác.
Trình chấm mẫu được cung cấp trong môi trường thi nhận dữ liệu theo định dạng:
X[i] Y[i], theo thứ tự \(i=0,\ldots,N-1\).Hàm DistanceSum trả về tổng khoảng cách giữa mọi cặp khối khác nhau, mỗi cặp tính một lần, theo mô đun \(1\,000\,000\,000\).
| Phân nhóm | Điểm | Điều kiện |
|---|---|---|
| 1 | 11 | \(N \le 200\). |
| 2 | 21 | \(N \le 2\,000\). |
| 3 | 23 | \(N \le 100\,000\). Với hai ô có khối \(i,j\) bất kỳ mà \(X[i]=X[j]\), mọi ô nằm giữa chúng trên cùng hàng đều không trống. Đồng thời, với hai ô có khối \(i,j\) bất kỳ mà \(Y[i]=Y[j]\), mọi ô nằm giữa chúng trên cùng cột đều không trống. |
| 4 | 45 | \(N \le 100\,000\). |
Ví dụ 1
Ví dụ 2
11
2 5
2 6
3 3
3 6
4 3
4 4
4 5
4 6
5 3
5 4
5 6
174
Thành phố lý tưởng gồm \(N=11\) khối: \(v_0=(2,5)\), \(v_1=(2,6)\), \(v_2=(3,3)\), \(v_3=(3,6)\), \(v_4=(4,3)\), \(v_5=(4,4)\), \(v_6=(4,5)\), \(v_7=(4,6)\), \(v_8=(5,3)\), \(v_9=(5,4)\) và \(v_{10}=(5,6)\).
Chẳng hạn, \(d(v_1,v_3)=1\), \(d(v_1,v_8)=6\), \(d(v_6,v_{10})=2\) và \(d(v_9,v_{10})=4\). Có \(11 \times 10/2=55\) cặp khối; tổng khoảng cách của tất cả các cặp là \(174\).
Leonardo rất hăng hái khi thực hiện Bữa tối cuối cùng, bức tranh tường nổi tiếng nhất của ông. Một trong những công việc đầu tiên mỗi ngày là quyết định các màu keo sẽ dùng trong phần còn lại của ngày. Ông cần nhiều màu nhưng chỉ có thể giữ một số lượng hạn chế trên giàn giáo. Người trợ lý có nhiệm vụ trèo lên đưa màu cho ông rồi trèo xuống đặt những màu được trả lại lên kệ dưới sàn.
Bạn phải viết hai chương trình riêng biệt để giúp người trợ lý. Chương trình thứ nhất nhận toàn bộ dãy màu Leonardo sẽ cần trong ngày và tạo ra một xâu bit ngắn gọi là chỉ dẫn. Trong ngày, người trợ lý chỉ có chỉ dẫn này, không được biết trước các yêu cầu tương lai. Chương trình thứ hai nhận chỉ dẫn rồi nhận và xử lý từng yêu cầu một; nó phải hiểu chỉ dẫn để đưa ra các lựa chọn tối ưu.
Có \(N\) màu, đánh số từ \(0\) đến \(N-1\). Mỗi ngày Leonardo yêu cầu màu đúng \(N\) lần. Gọi \(C\) là dãy \(N\) yêu cầu, mỗi phần tử thuộc \(0,\ldots,N-1\). Một màu có thể không xuất hiện trong \(C\), hoặc xuất hiện nhiều lần.
Giàn giáo luôn đầy và chứa \(K\) trong số \(N\) màu, với \(K<N\). Ban đầu, giàn giáo chứa các màu từ \(0\) đến \(K-1\), kể cả hai đầu. Các yêu cầu được xử lý lần lượt. Nếu màu được yêu cầu đã có trên giàn giáo, người trợ lý được nghỉ. Nếu chưa có, anh ta phải lấy màu đó từ kệ đưa lên giàn giáo, đồng thời chọn một màu đang có trên giàn giáo để mang xuống kệ vì không còn chỗ cho màu mới.
Người trợ lý muốn được nghỉ nhiều lần nhất. Mỗi khi phải đưa một màu xuống kệ, lựa chọn của anh ta có thể ảnh hưởng đến các yêu cầu về sau. Nếu biết trước \(C\), Leonardo chỉ ra cách chọn màu cần loại khỏi giàn giáo bằng cách xét những màu đang có và phần yêu cầu còn lại:
Có thể chứng minh chiến thuật này giúp người trợ lý được nghỉ nhiều lần nhất có thể.
Vào buổi sáng, người trợ lý xin Leonardo viết \(C\) lên giấy để xác định và làm theo chiến thuật tối ưu. Nhưng Leonardo muốn giữ bí mật kỹ thuật làm việc nên không cho anh ta giữ tờ giấy; ông chỉ cho đọc \(C\) và cố nhớ. Trí nhớ của người trợ lý rất kém, chỉ nhớ được tối đa \(M\) bit, thường không đủ để tái tạo toàn bộ \(C\). Anh ta cần tìm cách tính một dãy bit thích hợp để nhớ; dãy chỉ dẫn này được ký hiệu là \(A\).
Bạn phải viết hai chương trình riêng biệt bằng cùng một ngôn ngữ lập trình. Chúng được thực hiện nối tiếp nhau và không được liên lạc với nhau trong khi chạy. Chương trình đầu tiên dùng vào buổi sáng, nhận \(C\) và tạo \(A\). Chương trình thứ hai dùng trong ngày, nhận \(A\), rồi xử lý \(C\) theo từng yêu cầu: phải xử lý xong yêu cầu hiện tại trước khi nhận yêu cầu tiếp theo.
Trong chương trình thứ nhất, cài đặt ComputeAdvice(C, N, K, M). Đầu vào là mảng \(C\) gồm \(N\) số nguyên thuộc \(0,\ldots,N-1\), số màu \(K\) trên giàn giáo và số bit chỉ dẫn tối đa \(M\). Chương trình tạo dãy \(A\) dài không quá \(M\) bit, rồi gửi các bit theo đúng thứ tự bằng chương trình con được cung cấp:
WriteAdvice(B): nối bit \(B\) vào cuối dãy chỉ dẫn \(A\) hiện tại. Được gọi nhiều nhất \(M\) lần.Trong chương trình thứ hai, cài đặt Assist(A, N, K, R). Đầu vào gồm dãy chỉ dẫn \(A\), các số \(N,K\) như trên, và độ dài thực tế \(R\) của \(A\) tính bằng bit, với \(R \le M\). Chương trình thực hiện chiến thuật của người trợ lý bằng các chương trình con được cung cấp:
GetRequest(): trả về màu tiếp theo Leonardo yêu cầu; không tiết lộ thông tin về các yêu cầu tương lai.PutBack(T): đưa màu \(T\) từ giàn giáo xuống kệ. Chỉ được gọi khi \(T\) là một màu hiện có trên giàn giáo.Assist phải gọi GetRequest đúng \(N\) lần, nhận các yêu cầu theo thứ tự. Sau mỗi lần gọi, nếu màu nhận được chưa có trên giàn giáo, phải gọi PutBack(T) với màu được chọn để nhường chỗ cho màu vừa yêu cầu. Nếu màu đã có trên giàn giáo, không được gọi PutBack. Vi phạm quy tắc này bị coi là lỗi và chương trình sẽ bị kết thúc. Nhớ rằng giàn giáo ban đầu chứa các màu \(0,\ldots,K-1\).
Một bộ dữ liệu được coi là giải đúng nếu cả hai chương trình con tuân thủ mọi ràng buộc và tổng số lần gọi PutBack đúng bằng số lần của chiến thuật tối ưu của Leonardo. Nếu nhiều chiến thuật đạt cùng số lần gọi tối ưu, được dùng bất kỳ chiến thuật nào; không bắt buộc thực hiện đúng từng lựa chọn của Leonardo.
Trên LQDOJ, nộp đúng một tệp C hoặc C++ cài đặt cả ComputeAdvice và Assist. Trình chấm chạy hai phần trong hai tiến trình tách biệt: tiến trình thứ nhất gọi ComputeAdvice, còn tiến trình thứ hai chỉ nhận dãy chỉ dẫn rồi gọi Assist. Vì vậy, biến toàn cục và bộ nhớ của hai phần không được chia sẻ; cách duy nhất để truyền thông tin từ ComputeAdvice sang Assist là gọi WriteAdvice để tạo dãy chỉ dẫn nhận qua tham số A.
C/C++
void ComputeAdvice(int *C, int N, int K, int M);
void WriteAdvice(unsigned char a);
void Assist(unsigned char *A, int N, int K, int R);
void PutBack(int T);
int GetRequest();
Các chương trình con phải hoạt động như đã mô tả. Có thể viết thêm các chương trình con dùng nội bộ, nhưng nên khai báo chúng là static hoặc bảo đảm tên không trùng nhau. Bài nộp không được giao tiếp với đầu vào, đầu ra chuẩn hay tệp tin. Tệp supper.h, trình chấm mẫu và script biên dịch C/C++ được cung cấp trong phần đính kèm. Mã nguồn cần bắt đầu bằng:
# include "supper.h"
Trình chấm mẫu nhận dữ liệu như sau:
N K M.Trình chấm mẫu chạy ComputeAdvice trước và tạo tệp advice.txt, chứa từng bit chỉ dẫn, cách nhau bởi dấu cách và kết thúc bằng số 2. Sau đó, trình chấm chạy Assist và tạo đầu ra mà mỗi dòng có dạng R [number] hoặc P [number]. Dòng R [number] biểu thị lời gọi GetRequest() và giá trị nhận được; dòng P [number] biểu thị lời gọi PutBack() và màu được đưa xuống kệ. Đầu ra kết thúc bằng một dòng E.
Thời gian chạy với trình chấm chính thức có thể hơi khác thời gian trên máy cục bộ. Chênh lệch này không đáng kể, nhưng nên dùng giao diện chạy thử để kiểm tra lời giải có nằm trong giới hạn thời gian hay không.
| Phân nhóm | Điểm | Điều kiện |
|---|---|---|
| 1 | 8 | \(N \le 5\,000\); được dùng tối đa \(M=65\,000\) bit. |
| 2 | 9 | \(N \le 100\,000\); được dùng tối đa \(M=2\,000\,000\) bit. |
| 3 | 9 | \(N \le 100\,000\), \(K \le 25\,000\); được dùng tối đa \(M=1\,500\,000\) bit. |
| 4 | 35 | \(N \le 5\,000\); được dùng tối đa \(M=10\,000\) bit. |
| 5 | Tối đa 39 | \(N \le 100\,000\), \(K \le 25\,000\); được dùng tối đa \(M=1\,800\,000\) bit. |
Điểm phân nhóm \(5\) phụ thuộc vào độ dài chỉ dẫn được gửi. Gọi \(R_{\max}\) là độ dài lớn nhất của dãy chỉ dẫn do ComputeAdvice tạo ra trên tất cả các bộ dữ liệu của phân nhóm. Điểm được tính như sau:
Ví dụ 1
2 0 3 0
Có \(N=4\) màu, đánh số từ \(0\) đến \(3\), và \(4\) yêu cầu. Giàn giáo chứa được \(K=2\) màu, ban đầu là \([0,1]\). Một cách xử lý là:
Cách xử lý trên không tối ưu. Chiến thuật tối ưu của Leonardo đưa màu \(2\) xuống ở bước thứ ba, nhờ đó người trợ lý lại được nghỉ ở bước cuối.
Ví dụ 2
0 0 1 0 0 0 1 0 1 1 1 0 1 1 0 0
Vào buổi sáng, người trợ lý có thể đọc \(C\) và quyết định trước các lựa chọn. Một khả năng là ghi nhớ trạng thái giàn giáo sau mỗi yêu cầu. Với cách xử lý không tối ưu ở Ví dụ 1, các trạng thái là \([0,2]\), \([0,2]\), \([3,2]\), \([3,0]\); trạng thái ban đầu \([0,1]\) đã biết sẵn.
Giả sử \(M=16\). Vì \(N=4\), mỗi màu có thể lưu bằng \(2\) bit, nên \(16\) bit đủ lưu dãy trạng thái trên, tạo ra dãy \(A\) đã cho. Trong ngày, người trợ lý giải mã chỉ dẫn này để đưa ra các lựa chọn. Với \(M=16\), anh ta cũng có thể lưu toàn bộ \(C\) chỉ bằng \(8\) trong \(16\) bit. Ví dụ chỉ minh họa một cách biểu diễn khác; cách xử lý được biểu diễn vẫn là cách không tối ưu đã nêu.
Ví dụ 3
WriteAdvice(0)
WriteAdvice(0)
WriteAdvice(1)
WriteAdvice(0)
WriteAdvice(0)
WriteAdvice(0)
WriteAdvice(1)
WriteAdvice(0)
WriteAdvice(1)
WriteAdvice(1)
WriteAdvice(1)
WriteAdvice(0)
WriteAdvice(1)
WriteAdvice(1)
WriteAdvice(0)
WriteAdvice(0)
Tiếp tục Ví dụ 2, nếu ComputeAdvice tạo \(A=(0,0,1,0,0,0,1,0,1,1,1,0,1,1,0,0)\), thì gửi nó bằng đúng dãy lời gọi trên. Sau đó Assist nhận \(A\), \(N=4\), \(K=2\), \(R=16\) và phải gọi GetRequest đúng \(4\) lần. Sau một số yêu cầu, nó phải gọi PutBack(T) với màu thích hợp.
Bảng dưới đây tương ứng với các lựa chọn không tối ưu trong Ví dụ 1. Dấu gạch ngang nghĩa là không gọi PutBack.
Giá trị do GetRequest() trả về |
Hành động |
|---|---|
| \(2\) | PutBack(1) |
| \(0\) | — |
| \(3\) | PutBack(0) |
| \(0\) | PutBack(2) |
Trong đám cưới với Beatrice d'Este năm \(1491\), Công tước thành Milan Lodovico Sforza nhờ Leonardo tổ chức lễ hội, trong đó có một giải đấu thương lớn kéo dài suốt ba ngày. Thế nhưng hiệp sĩ được hâm mộ nhất lại đến muộn…
Ban đầu, \(N\) hiệp sĩ đứng thành một hàng, được đánh số vị trí từ \(0\) đến \(N-1\) theo thứ tự trong hàng. Trọng tài tổ chức một vòng đấu bằng cách gọi hai vị trí \(S,E\), với \(0 \le S<E \le N-1\). Tất cả hiệp sĩ từ vị trí \(S\) đến \(E\), kể cả hai đầu, cùng tranh tài. Người thắng tiếp tục thi đấu và trở về vị trí của mình; những người thua bị loại và rời hàng. Các hiệp sĩ còn lại dồn về đầu hàng, giữ nguyên thứ tự tương đối, để các vị trí mới liên tiếp từ \(0\) đến \(N-(E-S)-1\). Trọng tài tiếp tục gọi các vòng đấu theo cách này cho đến khi chỉ còn một hiệp sĩ.
Leonardo biết sức mạnh của các hiệp sĩ đôi một khác nhau, biểu diễn bằng thứ hạng từ \(0\) (yếu nhất) đến \(N-1\) (mạnh nhất). Ông cũng biết chính xác các cặp vị trí mà trọng tài sẽ gọi trong \(C\) vòng đấu: dù sao thì ông cũng là Leonardo! Trong mỗi vòng, hiệp sĩ có thứ hạng cao nhất chắc chắn thắng.
Có \(N-1\) hiệp sĩ đã xếp hàng; chỉ còn thiếu hiệp sĩ được hâm mộ nhất, có thứ hạng \(R\). Để tăng sức hấp dẫn của giải đấu, Leonardo muốn chọn vị trí chèn hiệp sĩ này vào hàng sao cho số vòng anh ta thắng là lớn nhất. Chỉ tính những vòng mà anh ta tham gia và thắng; những vòng anh ta không tham gia không được tính.
Cài đặt GetBestPosition(N, C, R, K, S, E) với các tham số:
Các cặp vị trí đều hợp lệ: \(E[i]\) nhỏ hơn số hiệp sĩ còn lại trước vòng thứ \(i+1\), và sau tất cả \(C\) vòng chỉ còn đúng một hiệp sĩ.
Hàm phải trả về vị trí tốt nhất \(P\), với \(0 \le P \le N-1\), để số vòng hiệp sĩ đến muộn thắng là lớn nhất. Nếu có nhiều vị trí cùng tối ưu, trả về vị trí nhỏ nhất. \(P\) là chỉ số bắt đầu từ \(0\) của hiệp sĩ đến muộn trong hàng sau khi chèn, hay chính là số hiệp sĩ đứng trước anh ta. \(P=0\) nghĩa là đầu hàng; \(P=N-1\) nghĩa là cuối hàng.
Nộp đúng một tệp tournament.c, tournament.cpp hoặc tournament.pas, cài đặt chương trình con với chữ ký sau.
C/C++
int GetBestPosition(int N, int C, int R, int *K, int *S, int *E);
Pascal
function GetBestPosition(N, C, R : LongInt; var K, S, E : array of LongInt) : LongInt;
Chương trình con phải hoạt động như đã mô tả. Bạn có thể cài đặt thêm các chương trình con dùng nội bộ. Bài nộp không được giao tiếp dưới bất kỳ hình thức nào với đầu vào/đầu ra chuẩn hoặc với bất kỳ tệp nào khác.
Trình chấm mẫu trong môi trường thi nhận dữ liệu như sau:
N C R.S[i] E[i], theo thứ tự \(i=0,\ldots,C-1\).Hàm GetBestPosition trả về vị trí \(P\) nhỏ nhất trong các vị trí cho số vòng thắng lớn nhất.
| Phân nhóm | Điểm | Điều kiện |
|---|---|---|
| 1 | 17 | \(N \le 500\). |
| 2 | 32 | \(N \le 5\,000\). |
| 3 | 51 | \(N \le 100\,000\). |
Ví dụ 1
5 3 3
1
0
2
4
1 3
0 1
0 1
1
Có \(N=5\) hiệp sĩ; bốn người đã có mặt có thứ hạng \([1,0,2,4]\), nên hiệp sĩ đến muộn có \(R=3\). Ba vòng đấu lần lượt dùng các cặp vị trí \((1,3)\), \((0,1)\), \((0,1)\).
Nếu chèn vào đầu hàng, dãy thứ hạng là \([3,1,0,2,4]\). Vòng đầu gồm các vị trí \(1,2,3\), có thứ hạng \(1,0,2\); hiệp sĩ hạng \(2\) thắng, để lại hàng \([3,2,4]\). Vòng tiếp theo, hạng \(3\) đấu hạng \(2\) ở vị trí \(0,1\), và hạng \(3\) thắng; hàng còn \([3,4]\). Vòng cuối ở vị trí \(0,1\) do hạng \(4\) thắng. Hiệp sĩ đến muộn chỉ thắng một vòng, là vòng thứ hai.
Nếu chèn giữa hai hiệp sĩ hạng \(1\) và \(0\), hàng là \([1,3,0,2,4]\). Vòng đầu gồm các hạng \(3,0,2\), do hạng \(3\) thắng, để lại \([1,3,4]\). Vòng tiếp theo, hạng \(1\) đấu hạng \(3\) và hạng \(3\) lại thắng. Hàng cuối là \([3,4]\), do hạng \(4\) thắng. Hiệp sĩ đến muộn thắng hai vòng; không có cách chèn nào giúp anh ta thắng nhiều hơn hai vòng. Vị trí nhỏ nhất đạt được kết quả này là \(1\).