<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>training &amp;mdash; Zumi&#39;s Blog</title>
    <link>https://blog.dtth.ch/nki/tag:training</link>
    <description>Just random Zumi Zoom things</description>
    <pubDate>Tue, 28 Apr 2026 20:40:17 +0200</pubDate>
    <item>
      <title>2017/04/28 Training</title>
      <link>https://blog.dtth.ch/nki/yyd5ub7sbm</link>
      <description>&lt;![CDATA[#training #apio #vietnamese #thầyHoàng #anhKhuê&#xA;&#xA;Hôm nay có hai phần: bài thầy Hoàng và bài anh Khuê. Cả 2 đều cần một sự tay to nhất định.&#xA;&#xA;!--more--&#xA;&#xA;Tóm tắt đề bài&#xA;select (thầy Hoàng)&#xA;Cho dãy $S$ gồm các kí tự d và x. 2 người lần lượt chơi, mỗi lượt bốc 1 kí tự, kí tự này phải đứng cạnh 1 vị trí đã bị chọn trước đó (trừ nước đầu tiên được chọn thoải mái). Khi trò chơi kết thúc, người đi trước thắng khi có nhiều d hơn hẳn người kia. Đếm số vị trí ban đầu mà người đi trước có thể chọn mà vẫn đảm bảo chiến thắng?&#xA;&#xA;Giới hạn&#xA;\\(1 \le |S| \le 1000\\).&#xA;&#xA;eureka (thầy Hoàng)&#xA;Cho một hệ thống cân đĩa được biểu diễn như sau:&#xA;&#xA;Nếu chỉ gồm -1: Đây là một vị trí đặt quả cân.&#xA;Là một cái cân có dạng x y A B trong đó \\(x\\) và \\(y\\) lần lượt là độ dài cánh tay đòn bên trái và bên phải của cân; \\(A\\) và \\(B\\) là 2 hệ thống cân được treo vào bên trái và bên phải của cân.&#xA;&#xA;Hình dưới biểu diễn hệ thống cân được biểu diễn bằng dãy 12 18 4 2 -1 -1 10 8 3 3 -1 -1 6 4 -1 -1:&#xA;&#xA;Trọng lượng các quả cân đều phải là số nguyên dương.&#xA;&#xA;Hãy tìm trọng lượng cho các quả cân của từng vị trí đặt sao cho tất cả các cân đều thăng bằng và tổng trọng lượng các quả cân phải đặt là nhỏ nhất. Giả sử cân không có trọng lượng.&#xA;&#xA;In ra tổng nhỏ nhất lấy dư cho \\(123456789\\).&#xA;&#xA;Giới hạn&#xA;Miêu tả được cho bởi dãy \\(A[1..N]\\).&#xA;&#xA;\\(1 \le N \le 3 \times 10^5\\), \\(A[i] = -1\\) hoặc \\(1 \le A[i] \le 100\\).&#xA;&#xA;liondance (thầy Hoàng)&#xA;Cho 2 dãy số \\(A[1..N]\\) và \\(B[1..N]\\). Tìm dãy con chung dài nhất sao cho 2 phần tử liên tiếp chênh lệch nhau không quá \\(d\\).&#xA;Nếu có nhiều dãy, in ra dãy có thứ tự từ điển lớn nhất.&#xA;&#xA;Giới hạn&#xA;\\(1 \le N \le 4000\\), \\(1 \le A[i], B[i] \le 10^9\\)&#xA;&#xA;desert (thầy Hoàng)&#xA;Trên mặt phẳng cho \\(N\\) điểm. Tìm đường đi từ \\(1\\) đến \\(N\\) sao cho khoảng cách Manhattan lớn nhất giữa 2 điểm liên tiếp đi qua là nhỏ nhất.&#xA;&#xA;Giới hạn&#xA;\\(1 \le N \le 10^5\\), tọa độ \\(-10^9 \le X\i, Y\i\le 10^9\\)&#xA;&#xA;Trốn việc (anh Khuê)&#xA;Cho dãy số \\(A[1..3N]\\), chọn ra tập số có tổng lớn nhất sao cho trong \\(N\\) số liên tiếp bất kì có không quá \\(K\\) số được chọn.&#xA;&#xA;Giới hạn&#xA;\\(1 \le N \le 200\\), \\(1 \le K \le 10\\), \\(1 \le A[i] \le 10^6\\)&#xA;&#xA;Party1 (anh Khuê)&#xA;Cho \\(N\\) bạn nam và \\(M\\) bạn nữ, và \\(K\\) mối quan hệ nam - nữ. Liệt kê tất cả bạn nam và bạn nữ chắc chắn sẽ xuất hiện trong cặp ghép cực đại.&#xA;&#xA;Giới hạn&#xA;\\(1 \le N, M \le 10^4\\), \\(1 \le K \le 10^5\\).&#xA;&#xA;Party2 (anh Khuê)&#xA;Cho đồ thị \\(N\\) đỉnh \\(M\\) cạnh xanh \\(K\\) cạnh đỏ, đỉnh \\(i\\) có trọng số \\(A[i]\\). Chọn một tập điểm có tổng trọng số lớn nhất thỏa mãn:&#xA; 2 đỉnh có cạnh xanh nối giữa thì không được cùng chọn. - 2 đỉnh có cạnh đỏ nối giữa thì cùng được chọn, hoặc cùng không được chọn.&#xA;&#xA;Đồng thời, đếm số cách chọn tối ưu.&#xA;&#xA;Giới hạn&#xA;\\(1 \le N \le 250\\), \\(0 \le M \le \frac{N(N-1)}{6}\\), \\(\frac{N(N-1)}{3} \le K \le \frac{N(N-1)}{2}\\)&#xA;&#xA;Lời giải&#xA;select&#xA;Chuyển đổi bài toán&#xA;Rất khó quản lí trạng thái thắng thua khi bài toán yêu cầu so sánh số lượng. Vì vậy, ta sẽ biến đổi bài toán một chút - mặc dù tính chất thắng - thua không thay đổi.&#xA;&#xA;Thay vì so sánh số lượng d của từng người, ta lấy số lượng d của người đi trước trừ đi người đi sau. Hiển nhiên người đi trước muốn hiệu dương - tức đối đa hóa nó, và người đi sau muốn tối thiểu hóa nó.&#xA;&#xA;Bởi vì đây là trò chơi hữu hạn bước, và 2 người đều phải chơi tối ưu, nên ta sẽ chỉ đi tìm một kết quả duy nhất: hiệu tối ưu của trò chơi, với mỗi cách chọn bước đầu.&#xA;&#xA;Quy hoạch động&#xA;Gọi \\(fl\\) là hiệu tối ưu của trò chơi, nếu trò chơi bắt đầu với trạng thái đoạn \\(l..r\\) bị khuyết. Ta có:&#xA;Hiển nhiên \\(fl = 0\\) nếu \\(l..r\\) đã phủ toàn bộ vòng tròn.&#xA;Ta có thể hiểu độ dài \\(l..r\\) sẽ tương đương với số bước đã xảy ra, vì vậy ta có thể biết được lượt đi tiếp theo là của ai.&#xA;Nếu đây là lượt của người đi trước, hẳn hắn ta sẽ muốn \\(fl\\) lớn nhất có thể, tức hắn sẽ chọn vị trí \\(l - 1\\) hay \\(r + 1\\) sao cho \\(fl - 1\\) hoặc \\(fl\\), cộng vị trí hắn chọn nếu nó màu đỏ, sao cho tổng ấy lớn nhất có thể. Nói cách khác ta có:&#xA;\\ f[l = \max(fl - 1 +&#xA;(\text{Sl - 1] == &#39;d&#39;}), f[l +&#xA;(\text{S[r + 1] == &#39;d&#39;}))\\]&#xA;Ngược lại, người đi sau sẽ muốn lựa chọn của mình là nhỏ nhất có thể, tức:&#xA;\\ f[l = \min(fl - 1 -&#xA;(\text{Sl - 1] == &#39;d&#39;}), f[l -&#xA;(\text{S[r + 1] == &#39;d&#39;}))\\]&#xA;Lưu ý dấu -, bởi ta đang tối ưu hiệu người đi trước trừ người đi sau.&#xA;&#xA;Ta có thể tính tất cả các hàm \\(f1\\), \\(f2\\), ..., \\(fN\\) trong độ phức tạp \\(O(N^2)\\).&#xA;&#xA;Đếm vị trí tốt&#xA;Một vị trí \\(i\\) thỏa mãn đầu bài nếu như hiệu tối ưu lớn hơn \\(0\\), bởi khi đó người thứ nhất luôn có thể làm cho mình có nhiều d hơn. Như vậy, ta chỉ cần thử hết các vị trí \\(i\\) và kiểm tra xem \\(fi   0\\) đúng không.&#xA;&#xA;Tổng độ phức tạp của bài toán là \\(O(N^2)\\).&#xA;&#xA;eureka&#xA;Tính chất của tổng trọng lượng&#xA;Xét một hệ thống cân, không khó để chứng minh tổng trọng lượng của nó phải là một bội của số \\(X\\) tương ứng với từng hệ thống.&#xA;&#xA;Nhiệm vụ của ta là đi tìm \\(X\\) đó cho cả hệ thống lớn, bằng cách tính từ các hệ thống con.&#xA;&#xA;Quy nạp&#xA;Hiển nhiên quả cân có thể nhận trọng lượng bất kì, vậy quả cân là hệ thống cân có \\(X\\) là 1.&#xA;&#xA;Ta xét hệ thống cân \\(a, b, X\l, X\r\\) trong đó \\(X\l, X\r\\) là các giá trị mình đã tính trước đó cho hệ thống cân bên trái và phải.&#xA;Ta có:&#xA;Tồn tại \\((p, q) = 1\\) sao cho \\(paX\l = qbX\r\\). Hiển nhiên đây là \\(p\\) và \\(q\\) có tổng nhỏ nhất thỏa mãn.&#xA;Khi đó, \\(X = pX\l + qX\r\\).&#xA;&#xA;Ta có thể nhận thấy \\(\frac{p}{q} = \frac{bX\r}{aX\l}\\), vậy \\(p = bX\r / d\\) và \\(q = aX\l / d\\) với \\(d = \gcd(aX\l, bX\r)\\).&#xA;Như vậy \\(X = \dfrac{X\l X\r(a + b)}{d}\\).&#xA;&#xA;Số lớn&#xA;Ta nhận thấy từ công thức trên rằng đáp số có thể rất lớn. Ta không cần phải in số lớn, tuy vậy các phép tính như \\(\gcd\\) không thể được tính khi số đã bị mod.&#xA;&#xA;Tuy nhiên ta cũng không cần phải cài số lớn: Mỗi lần ta nhân thêm một số \\(a + b \le 200\\), nên thay vì lưu số lớn ta sẽ lưu tập ước nguyên tố và số mũ. Khi đó, các phép nhân (cộng số mũ), chia (trừ số mũ), lấy \\(\gcd\\) (lấy min số mũ) trở nên đơn giản, với độ phức tạp \\(O(A[i])\\).&#xA;&#xA;Ta có thuật toán đáp số với độ phức tạp&#xA;\\(O(N  A[i])\\).&#xA;&#xA;Cài đặt&#xA;Một cách cài đặt ngắn gọn là sử dụng đệ quy, vừa đọc vừa làm.&#xA;Ta thực hiện lần lượt:&#xA;Đọc \\(a\\) và kiểm tra xem có phải quả cân không. Nếu có trả về 1.&#xA;Đọc \\(b\\).&#xA;Đệ quy giải bên trái \\(Xl\\).&#xA;Đệ quy giải bên phải \\(Xr\\).&#xA;Tính và trả về \\(X\\).&#xA;&#xA;liondance&#xA;Quy hoạch động&#xA;Về cơ bản, đây chỉ là bài toán LCS có thêm điều kiện, vì vậy hướng suy nghĩ của ta tất nhiên là cải thiện thuật toán quy hoạch động LCS, vì độ phức tạp yêu cầu cũng tương đương.&#xA;&#xA;Vậy làm sao để quản lí trạng thái vẫn là \\(i\\) khi ta còn cần thông tin về 2 phần tử liên tiếp? Ta sẽ cần &#34;gói&#34; nhiều thông tin hơn vào trạng thái \\((i, j)\\).&#xA;&#xA;Thêm thông tin!&#xA;Ta sẽ định nghĩa hàm quy hoạch động như sau:&#xA;Gọi \\(fi\\) là dãy con chung dài nhất khi sử dụng đoạn \\(A[i..N]\\), đoạn \\(B[i + 1..N]\\) và \\(B[j]\\) là phần tử cuối cùng được thêm ở đầu đoạn (để đơn giản, ta không tính cặp \\(B[j]\\) - ?? vào đáp số, cũng như coi \\( B[0]\\) là phần tử có thể ghép với mọi thứ).&#xA;&#xA;Tại sao lại định nghĩa như vậy?&#xA;Nhờ có việc đánh dấu \\(B[j]\\) là số được thêm vào cuối cùng, ta có thể quản lí giá trị có thể thêm vào tiếp theo.&#xA;Khi có \\(fi\\), và \\(A[i] = B[k]\\) (\\(k \ge j + 1\\)), điều kiện duy nhất đẻ kiểm tra chỉ là \\(|B[j] - B[k]| \le d\\).&#xA;&#xA;Tại sao lại từ cuối?&#xA;Để giúp cho việc truy vết thứ tự từ điển, sau này mình sẽ nói đến.&#xA;&#xA;Ta có thể tính hàm quy hoạch động này như sau - \\(fi\\) có thể được tính từ các trạng thái:&#xA;&#xA;\\(fi + 1\\), hiển nhiên&#xA;\\(fi + 1 + 1\\), với \\(k \ge j + 1\\) và \\(A[i] = B[k]\\).&#xA;&#xA;Độ phức tạp là \\(O(N^3)\\). Ta sẽ cần một chút quan sát để hạ độ phức tạp.&#xA;&#xA;Nhảy xuống \\(O(N^2)\\)&#xA;Khi xét \\(fi\\), ta có thể thấy nếu \\(k \le k&#39;\\) và \\(Ai] = B[k] = B[k&#39;]\\) thì \\(f[i + 1 \ge fi + 1\\). Chứng minh rất đơn giản: \\(B[k + 1..N]\\) dài hơn \\(B[k&#39; + 1..N]\\), mà điều kiện ghép không đổi.&#xA;&#xA;Vì vậy, thực chất ta chỉ cần tìm \\(k\\) nhỏ nhất thỏa mãn \\(k \ge j + 1\\) và \\(Ai] = B[k]\\) để cập nhật vào \\(f[i\\). Việc này có thể thực hiện bằng việc đánh dấu khi for ngược \\(i\\) và \\(j\\), nên độ phức tạp chỉ còn \\(O(N^2)\\).&#xA;&#xA;Thứ tự từ điển&#xA;Khi quy hoạch động \\(fi\\), ta sẽ lưu thêm \\(nxi\\) là chỉ số \\(B[k]\\) mà mình chọn làm kí tự tiếp theo. Do điều kiện của dãy đáp số là phụ thuộc vào giá trị* chứ không phải chỉ số, ta sẽ phải thêm vào các điều kiện sau đây để đảm bảo lựa chọn chỉ số tiếp theo cho truy vết:&#xA;Hiển nhiên nếu \\(fi + 1 \neq fi + 1\\) thì ta chọn cái nào lớn hơn, vì được dãy dài hơn.&#xA;Nếu bằng nhau, hiển nhiên trong \\(Bnx[i + 1]\\) và \\(B[k]\\) cái nào lớn hơn ta chọn.&#xA;Nếu vẫn bằng nhau, ta nhận thấy chắc chắn \\(nxi + 1 \ge k\\), do vậy từ \\((i + 1, k)\\) ta có nhiều lựa chọn hơn (và có tất cả lựa chọn của) \\((i + 1, nxi + 1)\\), nên ta sẽ chọn \\((i + 1, k)\\).&#xA;&#xA;Khi đã có mảng \\(nxi\\), việc truy vết trở thành đơn giản.&#xA;&#xA;desert&#xA;Cây khung Manhattan&#xA;Bản chất của việc dựng đường đi sao cho cạnh \\(\max\\) là \\(\min\\) chính là dựng cây khung nhỏ nhất của đồ thị.&#xA;&#xA;Từ việc chăm chỉ đọc wiki, ta cũng biết có thể dựng lên cây khung giữa các đỉnh theo khoảng cách Manhattan với độ phức tạp \\(O(N \log N)\\). Thực chất đây chỉ là bài yêu cầu implement thuật toán đó.&#xA;&#xA;Dựng cây khung như nào?&#xA;Ta có thể chứng minh rằng, để dựng cây khung Manhattan, với mỗi&#xA;điểm ta chỉ cần nối cạnh đến đỉnh gần nó nhất trong mỗi góc phần tám).&#xA;Vậy tìm chúng như thế nào?&#xA;&#xA;Tìm điểm gần nhất trong góc phần tám&#xA;Giả sử ta sẽ giải bài toán cho góc phần tám có \\(X \le X\0\\), \\(Y \le Y\0\\) và \\(X - Y \le X\0 - Y\0\\).&#xA;&#xA;Ta sắp xếp các điểm theo thứ tự \\(X\i - Y\i\\) giảm dần, và thêm vào một IT có tọa độ được xếp theo \\(X\i\\) giá trị \\(X\i + Y\i\\) lấy \\(\max\\).&#xA;&#xA;Với mỗi điểm, ta get trong IT giá trị \\(X\j + Y\j\\) lớn nhất với \\(X\j \le X\i\\), rồi cập nhật điểm đó vào IT. Điểm get ra sẽ là điểm gần nhất theo góc phần tám này.&#xA;&#xA;Để hiểu tại sao sắp xếp như vậy lại đúng, xem hình dưới.&#xA;&#xA;Phần được gọi khi xét điểm \\(A\\). Phần tô màu đã được thêm vào IT, phần màu xanh thể hiện phần được get trong IT&#xA;&#xA;Dễ dàng làm với góc phần tám đối diện, chỉ cần for tập điểm ngược lại.&#xA;&#xA;Giải nhanh tất cả các phần&#xA;Thay vì cài tất cả các trường hợp góc phần tám, ta có thể chỉ cần giải 2 góc đối diện như trên, rồi thực hiện phép quay 90 độ cho tất cả các điểm theo gốc, và tiếp tục giải.&#xA;&#xA;Hiển nhiên sau khi xoay 3 lần ta sẽ giải đủ 8 góc phần tám.&#xA;&#xA;Trốn việc&#xA;Liên tưởng&#xA;Trong một hệ thống \\(N\\) máy song song, ta phải đảm bảo trong khoảng thời gian \\(M\\) bất kì không có quá \\(N\\) thao tác được thực hiện trên bất cứ máy nào. Ta sẽ làm như nào?&#xA;&#xA;Một cách đơn giản là ra lệnh cho một máy, sau khi thực hiện một thao tác, sẽ ngủ trong \\(M\\) giây. Như vậy, không có khoảng thời gian \\(M\\) nào có một máy chạy 2 lần, vì thế không có chuyện có nhiều hơn \\(N\\) thao tác được chạy.&#xA;&#xA;Ta sẽ sử dụng ý tưởng này cho bài toán.&#xA;&#xA;Biến đổi bài toán&#xA;Đề bài yêu cầu ta chọn một tập lớn nhất sao cho cứ \\(N\\) phần tử liên tiếp bất kì có không quá \\(K\\) phần tử được chọn. Ta sẽ biến bài toán thành: Cho phép chạy \\(K\\) máy lựa chọn song song, mỗi phần tử chỉ được cho một máy lựa chọn, trong khoảng \\(N\\) bất kì không có máy nào chọn liên tiếp 2 phần tử.&#xA;&#xA;Nếu \\(K = 1\\), ta có thể sử dụng quy hoạch động \\(f[i] = \max(f[i - 1], f[i - N] + A[i])\\). Với \\(K\\) lớn hơn, ta sẽ phải tìm cách khác để đảm bảo mỗi phần tử chỉ được tối đa một máy lựa chọn.&#xA;&#xA;Hãy tưởng tượng một đường ống \\(3N + 1\\) đoạn liên tiếp nối thành một đường thẳng. Ngoài ra đường ống thứ \\(i\\) còn được nối với \\(\min( i + N, 3N + 1)\\). Từ đỉnh 1 ta cho \\(K\\) robot chạy về hướng \\(3N + 1\\). Các robot có thể đi đường \\(i\\) - \\(i + 1\\) thoải mái, nhưng mỗi đường đi \\(i\\) - \\(i + N\\) chỉ có thể cho 1 robot đi qua, đồng thời sẽ thu về \\(A[i]\\) đồng tiền. Ta cần thu về nhiều tiền nhất có thể.&#xA;&#xA;Nghe rất giống một bài luồng max cost.&#xA;&#xA;Dựng luồng&#xA;Ta áp dụng toàn bộ ý tưởng của đường ống vào luồng.&#xA;&#xA;\\(3N + 2\\) đỉnh, \\(0\\) là nguồn \\(3N + 1\\) là đích.&#xA;\\(0\\) =  \\(1\\): cap = \\(K\\), cost = \\(0\\)&#xA;Với \\(i   0\\), \\(i\\) =  \\(i + 1\\): cap = \\(inf\\), cost = \\(0\\)&#xA;Với \\(i   0\\), \\(i\\) =  \\(\min(i + N, 3N + 1)\\): cap = \\(1\\), cost = \\(A[i]\\)&#xA;&#xA;Trên mạng, ta tìm luồng max cost. Hiển nhiên luồng cực đại là \\(K\\), nhưng ta chỉ cần quan tâm đến cost tối đa. Cost chính là đáp số.&#xA;&#xA;Độ phức tạp là O(luồng 600 đỉnh 1000 cạnh).&#xA;&#xA;Party1&#xA;Thay đổi mục tiêu&#xA;Thay vì đi tìm những đỉnh mà xuất hiện trong mọi cặp ghép cực đại, ta sẽ tìm những đỉnh không có tính chất đó. Thật vậy, ta sẽ cần tìm những đỉnh mà khi bỏ nó đi, kích cỡ cặp ghép cực đại vẫn không thay đổi.&#xA;&#xA;Tính chất của cặp ghép&#xA;Đầu tiên, ta dựng cặp ghép cực đại trên đồ thị đã cho. Hiển nhiên các đỉnh không thuộc cặp ghép là các đỉnh cần tìm. Ta sẽ chỉ xét đến các đỉnh thuộc cặp ghép.&#xA;&#xA;Giả sử \\(v\0\\) là một đỉnh không được ghép. Xét đường tăng \\(v\0, v\1,..., v\{2k}\\). Hiển nhiên độ dài đường tăng phải chẵn, nếu không ta có thể tăng số cặp ghép, không thỏa mãn tính chất cặp ghép cực đại.&#xA;&#xA;Ta nhận thấy, nếu xóa đi 1 trong các đỉnh \\(v\2, v\4, ...,v\{2k}\\), ta sẽ tách đường tăng hiện tại ra thành 1 đường tăng lẻ, đồng thời số cặp ghép giảm đi 1. Tuy nhiến, do tồn tại đường tăng lẻ nên ta có thể đảo lại, làm tăng số cặp ghép về như cũ. Vì vậy \\(v\2, v\4, ..., v\{2k}\\) đều là các đỉnh cần tìm.&#xA;&#xA;Nếu một đỉnh \\(x\\) khi xóa đi không tạo ra đường tăng lẻ nào (nhưng làm giảm số cặp ghép), chắc chắn nó không phải đỉnh cần tìm.&#xA;&#xA;Các cạnh xanh là cạnh cặp ghép. Xét đường tăng DHCGBFA (tím xanh), nếu bỏ B, đường tăng DHCG sẽ là đường lẻ, tăng cặp ghép về như cũ. Tương tự với C và A. Đường tăng JEI (xanh dương-xanh) cũng vậy.&#xA;&#xA;Thuật toán&#xA;Trước tiên, tìm cặp ghép. Sau đó, ta BFS từ các đỉnh không được cặp ghép, đi theo các đường tăng, đánh dấu các đỉnh cùng phía được thăm.&#xA;&#xA;Do tính chất của đồ thị 2 phía nên ta chỉ cần 2 lần BFS, mỗi lần xuất phát từ tất cả các đính không được thăm trên cùng 1 phía.&#xA;&#xA;Độ phức tạp sẽ là \\(O(K \sqrt{N + M})\\) do cặp ghép.&#xA;&#xA;Party2&#xA;Nén đồ thị&#xA;Với tập đỉnh đỏ, ta sẽ gộp tất cả các đỉnh liên thông lại thành một, cộng tất cả các trọng số lại. Bài toán trở thành tìm tập độc lập cực đại trên đồ thị cạnh xanh.&#xA;&#xA;Đây là một bài NP-Hard, chưa thể giải với giới hạn \\(N \le 250\\).&#xA;&#xA;Số lượng đỉnh&#xA;Một chi tiết quan trọng là số lượng cạnh đỏ rất lớn. Với \\(N = 250\\) và \\(K = \frac{N(N-1)}{3}\\), chỉ có tối đa \\(46\\) đỉnh đã gộp (bao gồm 205 đỉnh có clique và 45 đỉnh bậc 0).&#xA;&#xA;Từ đây, ta có chút hi vọng với thuật backtrack.&#xA;&#xA;Đặt cận!&#xA;Đây là danh sách cận cần thiết để hi vọng qua được đống test(?):&#xA;Sort các đỉnh theo bậc rồi backtrack dần&#xA;Xử lí nhanh các đỉnh bị cấm&#xA;Loại bỏ các trường hợp khi tổng các đỉnh còn lại không lớn hơn max hiện tại&#xA;Tính riêng các tplt rồi nhân với nhau&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/nki/tag:training" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">training</span></a> <a href="/nki/tag:apio" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">apio</span></a> <a href="/nki/tag:vietnamese" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">vietnamese</span></a> <a href="/nki/tag:th%E1%BA%A7yHo%C3%A0ng" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">thầyHoàng</span></a> <a href="/nki/tag:anhKhu%C3%AA" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">anhKhuê</span></a></p>

<p>Hôm nay có hai phần: bài thầy Hoàng và bài anh Khuê. Cả 2 đều cần một sự tay to nhất định.</p>



<h1 id="tóm-tắt-đề-bài">Tóm tắt đề bài</h1>

<h2 id="select-thầy-hoàng">select (thầy Hoàng)</h2>

<p>Cho dãy $S$ gồm các kí tự <code>d</code> và <code>x</code>. 2 người lần lượt chơi, mỗi lượt bốc 1 kí tự, kí tự này phải đứng cạnh 1 vị trí đã bị chọn trước đó (trừ nước đầu tiên được chọn thoải mái). Khi trò chơi kết thúc, người đi trước thắng khi có nhiều <code>d</code> <strong>hơn hẳn</strong> người kia. Đếm số vị trí ban đầu mà người đi trước có thể chọn mà vẫn đảm bảo chiến thắng?</p>

<h4 id="giới-hạn">Giới hạn</h4>

<p>\(1 \le |S| \le 1000\).</p>

<h2 id="eureka-thầy-hoàng">eureka (thầy Hoàng)</h2>

<p>Cho một hệ thống cân đĩa được biểu diễn như sau:</p>
<ul><li>Nếu chỉ gồm <code>-1</code>: Đây là một vị trí đặt quả cân.</li>
<li>Là một cái cân có dạng <code>x y A B</code> trong đó \(x\) và \(y\) lần lượt là độ dài cánh tay đòn bên trái và bên phải của cân; \(A\) và \(B\) là 2 hệ thống cân được treo vào bên trái và bên phải của cân.</li></ul>

<p>Hình dưới biểu diễn hệ thống cân được biểu diễn bằng dãy <code>12 18 4 2 -1 -1 10 8 3 3 -1 -1 6 4 -1 -1</code>:</p>

<p><img src="https://cdn.discordapp.com/attachments/676817846617243658/1108881431742124143/eureka_exp.png" alt=""></p>

<p>Trọng lượng các quả cân đều phải là số nguyên dương.</p>

<p>Hãy tìm trọng lượng cho các quả cân của từng vị trí đặt sao cho tất cả các cân đều thăng bằng và tổng trọng lượng các quả cân phải đặt là nhỏ nhất. Giả sử cân không có trọng lượng.</p>

<p>In ra tổng nhỏ nhất lấy dư cho \(123456789\).</p>

<h4 id="giới-hạn-1">Giới hạn</h4>

<p>Miêu tả được cho bởi dãy \(A[1..N]\).</p>

<p>\(1 \le N \le 3 \times 10^5\), \(A[i] = -1\) hoặc \(1 \le A[i] \le 100\).</p>

<h2 id="liondance-thầy-hoàng">liondance (thầy Hoàng)</h2>

<p>Cho 2 dãy số \(A[1..N]\) và \(B[1..N]\). Tìm dãy con chung dài nhất sao cho 2 phần tử liên tiếp chênh lệch nhau không quá \(d\).
Nếu có nhiều dãy, in ra dãy có <strong>thứ tự từ điển</strong> lớn nhất.</p>

<h4 id="giới-hạn-2">Giới hạn</h4>

<p>\(1 \le N \le 4000\), \(1 \le A[i], B[i] \le 10^9\)</p>

<h2 id="desert-thầy-hoàng">desert (thầy Hoàng)</h2>

<p>Trên mặt phẳng cho \(N\) điểm. Tìm đường đi từ \(1\) đến \(N\) sao cho khoảng cách Manhattan lớn nhất giữa 2 điểm liên tiếp đi qua là nhỏ nhất.</p>

<h4 id="giới-hạn-3">Giới hạn</h4>

<p>\(1 \le N \le 10^5\), tọa độ \(-10^9 \le X_i, Y_i\le 10^9\)</p>

<h2 id="trốn-việc-anh-khuê">Trốn việc (anh Khuê)</h2>

<p>Cho dãy số \(A[1..3N]\), chọn ra tập số có tổng lớn nhất sao cho trong \(N\) số liên tiếp bất kì có không quá \(K\) số được chọn.</p>

<h4 id="giới-hạn-4">Giới hạn</h4>

<p>\(1 \le N \le 200\), \(1 \le K \le 10\), \(1 \le A[i] \le 10^6\)</p>

<h2 id="party1-anh-khuê">Party1 (anh Khuê)</h2>

<p>Cho \(N\) bạn nam và \(M\) bạn nữ, và \(K\) mối quan hệ nam – nữ. Liệt kê tất cả bạn nam và bạn nữ chắc chắn sẽ xuất hiện trong cặp ghép cực đại.</p>

<h4 id="giới-hạn-5">Giới hạn</h4>

<p>\(1 \le N, M \le 10^4\), \(1 \le K \le 10^5\).</p>

<h2 id="party2-anh-khuê">Party2 (anh Khuê)</h2>

<p>Cho đồ thị \(N\) đỉnh \(M\) cạnh xanh \(K\) cạnh đỏ, đỉnh \(i\) có trọng số \(A[i]\). Chọn một tập điểm có tổng trọng số lớn nhất thỏa mãn:
 – 2 đỉnh có cạnh xanh nối giữa thì không được cùng chọn. – 2 đỉnh có cạnh đỏ nối giữa thì cùng được chọn, hoặc cùng không được chọn.</p>

<p>Đồng thời, đếm số cách chọn tối ưu.</p>

<h4 id="giới-hạn-6">Giới hạn</h4>

<p>\(1 \le N \le 250\), \(0 \le M \le \frac{N(N-1)}{6}\), \(\frac{N(N-1)}{3} \le K \le \frac{N(N-1)}{2}\)</p>

<h1 id="lời-giải">Lời giải</h1>

<h2 id="select">select</h2>

<h3 id="chuyển-đổi-bài-toán">Chuyển đổi bài toán</h3>

<p>Rất khó quản lí trạng thái thắng thua khi bài toán yêu cầu so sánh số lượng. Vì vậy, ta sẽ biến đổi bài toán một chút – mặc dù tính chất thắng – thua không thay đổi.</p>

<p>Thay vì so sánh số lượng <code>d</code> của từng người, ta lấy số lượng <code>d</code> của người đi trước trừ đi người đi sau. Hiển nhiên người đi trước muốn hiệu dương – tức <em>đối đa hóa</em> nó, và người đi sau muốn tối thiểu hóa nó.</p>

<p>Bởi vì đây là trò chơi hữu hạn bước, và 2 người đều phải chơi tối ưu, nên ta sẽ chỉ đi tìm một kết quả duy nhất: hiệu tối ưu của trò chơi, với mỗi cách chọn bước đầu.</p>

<h3 id="quy-hoạch-động">Quy hoạch động</h3>

<p>Gọi \(f[l][r]\) là hiệu tối ưu của trò chơi, nếu trò chơi bắt đầu với trạng thái đoạn \(l..r\) bị khuyết. Ta có:
– Hiển nhiên \(f[l][r] = 0\) nếu \(l..r\) đã phủ toàn bộ vòng tròn.
– Ta có thể hiểu độ dài \(l..r\) sẽ tương đương với số bước đã xảy ra, vì vậy ta có thể biết được lượt đi tiếp theo là của ai.
– Nếu đây là lượt của người đi trước, hẳn hắn ta sẽ muốn \(f[l][r]\) lớn nhất có thể, tức hắn sẽ chọn vị trí \(l – 1\) hay \(r + 1\) sao cho \(f[l – 1][r]\) hoặc \(f[l][r + 1]\), cộng vị trí hắn chọn nếu nó màu đỏ, sao cho tổng ấy lớn nhất có thể. Nói cách khác ta có:
\[ f[l][r] = \max(f[l – 1][r] +
(\text{S[l – 1] == &#39;d&#39;}), f[l][r + 1] +
(\text{S[r + 1] == &#39;d&#39;}))\]
– Ngược lại, người đi sau sẽ muốn lựa chọn của mình là nhỏ nhất có thể, tức:
\[ f[l][r] = \min(f[l – 1][r] -
(\text{S[l – 1] == &#39;d&#39;}), f[l][r + 1] -
(\text{S[r + 1] == &#39;d&#39;}))\]
Lưu ý dấu <code>-</code>, bởi ta đang tối ưu hiệu người đi trước trừ người đi sau.</p>

<p>Ta có thể tính tất cả các hàm \(f[1][1]\), \(f[2][2]\), ..., \(f[N][N]\) trong độ phức tạp \(O(N^2)\).</p>

<h3 id="đếm-vị-trí-tốt">Đếm vị trí tốt</h3>

<p>Một vị trí \(i\) thỏa mãn đầu bài nếu như hiệu tối ưu lớn hơn \(0\), bởi khi đó người thứ nhất luôn có thể làm cho mình có nhiều <code>d</code> hơn. Như vậy, ta chỉ cần thử hết các vị trí \(i\) và kiểm tra xem \(f[i][i] &gt; 0\) đúng không.</p>

<p>Tổng độ phức tạp của bài toán là \(O(N^2)\).</p>

<h2 id="eureka">eureka</h2>

<h3 id="tính-chất-của-tổng-trọng-lượng">Tính chất của tổng trọng lượng</h3>

<p>Xét một hệ thống cân, không khó để chứng minh tổng trọng lượng của nó phải là một bội của số \(X\) tương ứng với từng hệ thống.</p>

<p>Nhiệm vụ của ta là đi tìm \(X\) đó cho cả hệ thống lớn, bằng cách tính từ các hệ thống con.</p>

<h3 id="quy-nạp">Quy nạp</h3>

<p>Hiển nhiên quả cân có thể nhận trọng lượng bất kì, vậy quả cân là hệ thống cân có \(X\) là 1.</p>

<p>Ta xét hệ thống cân \(a, b, X_l, X_r\) trong đó \(X_l, X_r\) là các giá trị mình đã tính trước đó cho hệ thống cân bên trái và phải.
Ta có:
– Tồn tại \((p, q) = 1\) sao cho \(paX_l = qbX_r\). Hiển nhiên đây là \(p\) và \(q\) có tổng nhỏ nhất thỏa mãn.
– Khi đó, \(X = pX_l + qX_r\).</p>

<p>Ta có thể nhận thấy \(\frac{p}{q} = \frac{bX_r}{aX_l}\), vậy \(p = bX_r / d\) và \(q = aX_l / d\) với \(d = \gcd(aX_l, bX_r)\).
Như vậy \(X = \dfrac{X_l X_r(a + b)}{d}\).</p>

<h3 id="số-lớn">Số lớn</h3>

<p>Ta nhận thấy từ công thức trên rằng đáp số có thể rất lớn. Ta không cần phải in số lớn, tuy vậy các phép tính như \(\gcd\) không thể được tính khi số đã bị mod.</p>

<p>Tuy nhiên ta cũng không cần phải cài số lớn: Mỗi lần ta nhân thêm một số \(a + b \le 200\), nên thay vì lưu số lớn ta sẽ lưu tập ước nguyên tố và số mũ. Khi đó, các phép nhân (cộng số mũ), chia (trừ số mũ), lấy \(\gcd\) (lấy min số mũ) trở nên đơn giản, với độ phức tạp \(O(A[i])\).</p>

<p>Ta có thuật toán đáp số với độ phức tạp
\(O(N * A[i])\).</p>

<h3 id="cài-đặt">Cài đặt</h3>

<p>Một cách cài đặt ngắn gọn là sử dụng đệ quy, vừa đọc vừa làm.
Ta thực hiện lần lượt:
– Đọc \(a\) và kiểm tra xem có phải quả cân không. Nếu có trả về 1.
– Đọc \(b\).
– Đệ quy giải bên trái \(X<em>l\).
– Đệ quy giải bên phải \(X</em>r\).
– Tính và trả về \(X\).</p>

<h2 id="liondance">liondance</h2>

<h3 id="quy-hoạch-động-1">Quy hoạch động</h3>

<p>Về cơ bản, đây chỉ là bài toán LCS có thêm điều kiện, vì vậy hướng suy nghĩ của ta tất nhiên là cải thiện thuật toán quy hoạch động LCS, vì độ phức tạp yêu cầu cũng tương đương.</p>

<p>Vậy làm sao để quản lí trạng thái vẫn là \([i][j]\) khi ta còn cần thông tin về 2 phần tử liên tiếp? Ta sẽ cần “gói” nhiều thông tin hơn vào trạng thái \((i, j)\).</p>

<h3 id="thêm-thông-tin">Thêm thông tin!</h3>

<p>Ta sẽ định nghĩa hàm quy hoạch động như sau:
Gọi \(f[i][j]\) là dãy con chung dài nhất khi sử dụng đoạn \(A[i..N]\), đoạn \(B[i + 1..N]\) <strong>và \(B[j]\) là phần tử cuối cùng được thêm ở đầu đoạn</strong> (để đơn giản, ta không tính cặp \(B[j]\) – ?? vào đáp số, cũng như coi \( B[0]\) là phần tử có thể ghép với mọi thứ).</p>

<h4 id="tại-sao-lại-định-nghĩa-như-vậy">Tại sao lại định nghĩa như vậy?</h4>

<p>Nhờ có việc đánh dấu \(B[j]\) là số được thêm vào cuối cùng, ta có thể quản lí giá trị có thể thêm vào tiếp theo.
Khi có \(f[i][j]\), và \(A[i] = B[k]\) (\(k \ge j + 1\)), điều kiện duy nhất đẻ kiểm tra chỉ là \(|B[j] – B[k]| \le d\).</p>

<h4 id="tại-sao-lại-từ-cuối">Tại sao lại từ cuối?</h4>

<p>Để giúp cho việc truy vết thứ tự từ điển, sau này mình sẽ nói đến.</p>

<p>Ta có thể tính hàm quy hoạch động này như sau – \(f[i][j]\) có thể được tính từ các trạng thái:</p>
<ul><li>\(f[i + 1][j]\), hiển nhiên</li>
<li>\(f[i + 1][k] + 1\), với \(k \ge j + 1\) và \(A[i] = B[k]\).</li></ul>

<p>Độ phức tạp là \(O(N^3)\). Ta sẽ cần một chút quan sát để hạ độ phức tạp.</p>

<h3 id="nhảy-xuống-o-n-2">Nhảy xuống \(O(N^2)\)</h3>

<p>Khi xét \(f[i][j]\), ta có thể thấy nếu \(k \le k&#39;\) và \(A[i] = B[k] = B[k&#39;]\) thì \(f[i + 1][k] \ge f[i + 1][k&#39;]\). Chứng minh rất đơn giản: \(B[k + 1..N]\) dài hơn \(B[k&#39; + 1..N]\), mà điều kiện ghép không đổi.</p>

<p>Vì vậy, thực chất ta chỉ cần tìm \(k\) nhỏ nhất thỏa mãn \(k \ge j + 1\) và \(A[i] = B[k]\) để cập nhật vào \(f[i][j]\). Việc này có thể thực hiện bằng việc đánh dấu khi for ngược \(i\) và \(j\), nên độ phức tạp chỉ còn \(O(N^2)\).</p>

<h3 id="thứ-tự-từ-điển">Thứ tự từ điển</h3>

<p>Khi quy hoạch động \(f[i][j]\), ta sẽ lưu thêm \(nx[i][j]\) là chỉ số \(B[k]\) mà mình chọn làm kí tự tiếp theo. Do điều kiện của dãy đáp số là phụ thuộc vào <em>giá trị</em> chứ không phải chỉ số, ta sẽ phải thêm vào các điều kiện sau đây để đảm bảo lựa chọn chỉ số tiếp theo cho truy vết:
– Hiển nhiên nếu \(f[i + 1][j] \neq f[i + 1][k]\) thì ta chọn cái nào lớn hơn, vì được dãy dài hơn.
– Nếu bằng nhau, hiển nhiên trong \(B[nx[i + 1][j]]\) và \(B[k]\) cái nào lớn hơn ta chọn.
– Nếu vẫn bằng nhau, ta nhận thấy chắc chắn \(nx[i + 1][j] \ge k\), do vậy từ \((i + 1, k)\) ta có nhiều lựa chọn hơn (và có tất cả lựa chọn của) \((i + 1, nx[i + 1][j])\), nên ta sẽ chọn \((i + 1, k)\).</p>

<p>Khi đã có mảng \(nx[i][j]\), việc truy vết trở thành đơn giản.</p>

<h2 id="desert">desert</h2>

<h3 id="cây-khung-manhattan">Cây khung Manhattan</h3>

<p>Bản chất của việc dựng đường đi sao cho cạnh \(\max\) là \(\min\) chính là dựng cây khung nhỏ nhất của đồ thị.</p>

<p>Từ việc chăm chỉ đọc wiki, ta cũng biết có thể dựng lên cây khung giữa các đỉnh theo khoảng cách Manhattan với độ phức tạp \(O(N \log N)\). Thực chất đây chỉ là bài yêu cầu implement thuật toán đó.</p>

<h3 id="dựng-cây-khung-như-nào">Dựng cây khung như nào?</h3>

<p>Ta có thể <a href="https://www.topcoder.com/community/data-science/data-science-tutorials/line-sweep-algorithms/" rel="nofollow">chứng minh</a> rằng, để dựng cây khung Manhattan, với mỗi
điểm ta chỉ cần nối cạnh đến đỉnh gần nó nhất trong mỗi <a href="https://www.wikiwand.com/en/Octant_(plane_geometry)" rel="nofollow">góc phần tám</a>.
Vậy tìm chúng như thế nào?</p>

<h4 id="tìm-điểm-gần-nhất-trong-góc-phần-tám">Tìm điểm gần nhất trong góc phần tám</h4>

<p>Giả sử ta sẽ giải bài toán cho góc phần tám có \(X \le X_0\), \(Y \le Y_0\) và \(X – Y \le X_0 – Y_0\).</p>

<p>Ta sắp xếp các điểm theo thứ tự \(X_i – Y_i\) giảm dần, và thêm vào một IT có tọa độ được xếp theo \(X_i\) giá trị \(X_i + Y_i\) lấy \(\max\).</p>

<p>Với mỗi điểm, ta <code>get</code> trong IT giá trị \(X_j + Y_j\) lớn nhất với \(X_j \le X_i\), rồi cập nhật điểm đó vào IT. Điểm <code>get</code> ra sẽ là điểm gần nhất theo góc phần tám này.</p>

<p>Để hiểu tại sao sắp xếp như vậy lại đúng, xem hình dưới.</p>

<p><img src="https://cdn.discordapp.com/attachments/676817846617243658/1108881675724783716/desert_tree_upd.png" alt="Phần được gọi khi xét điểm \\(A\\). Phần tô màu đã được thêm vào IT, phần màu xanh thể hiện phần được get trong IT"></p>

<p>Dễ dàng làm với góc phần tám đối diện, chỉ cần for tập điểm ngược lại.</p>

<h4 id="giải-nhanh-tất-cả-các-phần">Giải nhanh tất cả các phần</h4>

<p>Thay vì cài tất cả các trường hợp góc phần tám, ta có thể chỉ cần giải 2 góc đối diện như trên, rồi thực hiện <a href="https://quizlet.com/6704360/rotation-rules-for-mrs-nelsons-geometry-flash-cards/" rel="nofollow">phép quay 90 độ</a> cho tất cả các điểm theo gốc, và tiếp tục giải.</p>

<p>Hiển nhiên sau khi xoay 3 lần ta sẽ giải đủ 8 góc phần tám.</p>

<h2 id="trốn-việc">Trốn việc</h2>

<h3 id="liên-tưởng">Liên tưởng</h3>

<p>Trong một hệ thống \(N\) máy song song, ta phải đảm bảo trong khoảng thời gian \(M\) bất kì không có quá \(N\) thao tác được thực hiện trên bất cứ máy nào. Ta sẽ làm như nào?</p>

<p>Một cách đơn giản là ra lệnh cho một máy, sau khi thực hiện một thao tác, sẽ ngủ trong \(M\) giây. Như vậy, không có khoảng thời gian \(M\) nào có một máy chạy 2 lần, vì thế không có chuyện có nhiều hơn \(N\) thao tác được chạy.</p>

<p>Ta sẽ sử dụng ý tưởng này cho bài toán.</p>

<h3 id="biến-đổi-bài-toán">Biến đổi bài toán</h3>

<p>Đề bài yêu cầu ta chọn một tập lớn nhất sao cho cứ \(N\) phần tử liên tiếp bất kì có không quá \(K\) phần tử được chọn. Ta sẽ biến bài toán thành: Cho phép chạy \(K\) máy lựa chọn song song, mỗi phần tử chỉ được cho một máy lựa chọn, trong khoảng \(N\) bất kì không có máy nào chọn liên tiếp 2 phần tử.</p>

<p>Nếu \(K = 1\), ta có thể sử dụng quy hoạch động \(f[i] = \max(f[i – 1], f[i – N] + A[i])\). Với \(K\) lớn hơn, ta sẽ phải tìm cách khác để đảm bảo mỗi phần tử chỉ được tối đa một máy lựa chọn.</p>

<p>Hãy tưởng tượng một đường ống \(3N + 1\) đoạn liên tiếp nối thành một đường thẳng. Ngoài ra đường ống thứ \(i\) còn được nối với \(\min( i + N, 3N + 1)\). Từ đỉnh 1 ta cho \(K\) robot chạy về hướng \(3N + 1\). Các robot có thể đi đường \(i\) – \(i + 1\) thoải mái, nhưng mỗi đường đi \(i\) – \(i + N\) chỉ có thể cho 1 robot đi qua, đồng thời sẽ thu về \(A[i]\) đồng tiền. Ta cần thu về nhiều tiền nhất có thể.</p>

<p>Nghe rất giống một bài luồng max cost.</p>

<h3 id="dựng-luồng">Dựng luồng</h3>

<p>Ta áp dụng toàn bộ ý tưởng của đường ống vào luồng.</p>
<ul><li>\(3N + 2\) đỉnh, \(0\) là nguồn \(3N + 1\) là đích.</li>
<li>\(0\) =&gt; \(1\): cap = \(K\), cost = \(0\)</li>
<li>Với \(i &gt; 0\), \(i\) =&gt; \(i + 1\): cap = \(inf\), cost = \(0\)</li>
<li>Với \(i &gt; 0\), \(i\) =&gt; \(\min(i + N, 3N + 1)\): cap = \(1\), cost = \(A[i]\)</li></ul>

<p>Trên mạng, ta tìm luồng max cost. Hiển nhiên luồng cực đại là \(K\), nhưng ta chỉ cần quan tâm đến cost tối đa. Cost chính là đáp số.</p>

<p>Độ phức tạp là <code>O(luồng 600 đỉnh 1000 cạnh)</code>.</p>

<h2 id="party1">Party1</h2>

<h3 id="thay-đổi-mục-tiêu">Thay đổi mục tiêu</h3>

<p>Thay vì đi tìm những đỉnh mà xuất hiện trong mọi cặp ghép cực đại, ta sẽ tìm những đỉnh không có tính chất đó. Thật vậy, ta sẽ cần tìm những đỉnh mà khi bỏ nó đi, kích cỡ cặp ghép cực đại vẫn không thay đổi.</p>

<h3 id="tính-chất-của-cặp-ghép">Tính chất của cặp ghép</h3>

<p>Đầu tiên, ta dựng cặp ghép cực đại trên đồ thị đã cho. Hiển nhiên các đỉnh không thuộc cặp ghép là các đỉnh cần tìm. Ta sẽ chỉ xét đến các đỉnh thuộc cặp ghép.</p>

<p>Giả sử \(v_0\) là một đỉnh không được ghép. Xét đường tăng \(v_0, v_1,..., v_{2k}\). Hiển nhiên độ dài đường tăng phải chẵn, nếu không ta có thể tăng số cặp ghép, không thỏa mãn tính chất cặp ghép cực đại.</p>

<p>Ta nhận thấy, nếu xóa đi 1 trong các đỉnh \(v_2, v_4, ...,v_{2k}\), ta sẽ tách đường tăng hiện tại ra thành 1 đường tăng lẻ, đồng thời số cặp ghép giảm đi 1. Tuy nhiến, do tồn tại đường tăng lẻ nên ta có thể đảo lại, làm tăng số cặp ghép về như cũ. Vì vậy \(v_2, v_4, ..., v_{2k}\) đều là các đỉnh cần tìm.</p>

<p>Nếu một đỉnh \(x\) khi xóa đi không tạo ra đường tăng lẻ nào (nhưng làm giảm số cặp ghép), chắc chắn nó không phải đỉnh cần tìm.</p>

<p><img src="https://cdn.discordapp.com/attachments/676817846617243658/1108881748743434240/party1_exp.png" alt="Các cạnh xanh là cạnh cặp ghép. Xét đường tăng DHCGBFA (tím xanh), nếu bỏ B, đường tăng DHCG sẽ là đường lẻ, tăng cặp ghép về như cũ. Tương tự với C và A. Đường tăng JEI (xanh dương-xanh) cũng vậy."></p>

<h3 id="thuật-toán">Thuật toán</h3>

<p>Trước tiên, tìm cặp ghép. Sau đó, ta BFS từ các đỉnh không được cặp ghép, đi theo các đường tăng, đánh dấu các đỉnh cùng phía được thăm.</p>

<p>Do tính chất của đồ thị 2 phía nên ta chỉ cần 2 lần BFS, mỗi lần xuất phát từ tất cả các đính không được thăm trên cùng 1 phía.</p>

<p>Độ phức tạp sẽ là \(O(K \sqrt{N + M})\) do cặp ghép.</p>

<h2 id="party2">Party2</h2>

<h3 id="nén-đồ-thị">Nén đồ thị</h3>

<p>Với tập đỉnh đỏ, ta sẽ gộp tất cả các đỉnh liên thông lại thành một, cộng tất cả các trọng số lại. Bài toán trở thành tìm tập độc lập cực đại trên đồ thị cạnh xanh.</p>

<p>Đây là một bài <strong>NP-Hard</strong>, chưa thể giải với giới hạn \(N \le 250\).</p>

<h3 id="số-lượng-đỉnh">Số lượng đỉnh</h3>

<p>Một chi tiết quan trọng là số lượng cạnh đỏ rất lớn. Với \(N = 250\) và \(K = \frac{N(N-1)}{3}\), chỉ có tối đa \(46\) đỉnh đã gộp (bao gồm 205 đỉnh có clique và 45 đỉnh bậc 0).</p>

<p>Từ đây, ta có chút hi vọng với thuật backtrack.</p>

<h3 id="đặt-cận">Đặt cận!</h3>

<p>Đây là danh sách cận cần thiết để hi vọng qua được đống test(?):
– Sort các đỉnh theo bậc rồi backtrack dần
– Xử lí nhanh các đỉnh bị cấm
– Loại bỏ các trường hợp khi tổng các đỉnh còn lại không lớn hơn max hiện tại
– Tính riêng các tplt rồi nhân với nhau</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/yyd5ub7sbm</guid>
      <pubDate>Fri, 28 Apr 2017 18:00:00 +0000</pubDate>
    </item>
    <item>
      <title>2017/04/21 Training</title>
      <link>https://blog.dtth.ch/nki/2017-04-21-training</link>
      <description>&lt;![CDATA[#training #apio #vietnamese #thầyNghĩa&#xA;&#xA;Tóm tắt đề bài&#xA;SEQUENCE&#xA;Cho dãy số A[1..N]. Mỗi lần xóa ta sẽ xóa tất cả các số mang một giá trị x nào đó. Hỏi dãy dài nhất có thể tạo ra được mà không tồn tại i &lt; j &lt; k thỏa mãn A[i] == A[k] &amp;&amp; A[i] != A[j]?&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 10^5, 1 &lt;= A[i] &lt;= 100&#xA;AVTOGAME&#xA;Cho xâu S. Mỗi bước ta có thể chọn một đoạn l &lt; r sao cho S[l] == S[r] và xóa đoạn đó khỏi xâu. Hỏi xâu ngắn nhất và dài nhất có thể tạo được (mà không thể xóa được tiếp) là bao nhiêu?&#xA;Giới hạn&#xA;10 test, 1 &lt;= |S| &lt;= 100, &#39;a&#39; &lt;= S[i] &lt;= &#39;p&#39;&#xA;DISKGAME&#xA;Cho một đĩa gồm N tầng xoay, mỗi tầng có K nấc xoay như hình dưới.&#xA;&#xA;Một đĩa có 3 tầng, mỗi tầng có 8 nấc&#xA;&#xA;Mỗi bước ta được xoay 1 tầng sang trái hoặc phải 1 nấc. Hỏi số bước nhỏ nhất để tạo ra 1 cột có các số bằng nhau là bao nhiêu?&#xA;&#xA;Một cách giải hình trên&#xA;Giới hạn&#xA;1 &lt;= N, K &lt;= 2000&#xA;&#xA;!--more--&#xA;&#xA;Lời giải&#xA;SEQUENCE&#xA;Điều kiện của dãy số&#xA;Ta có thể thấy, 2 số x và y không được cùng tồn tại trong đáp án nếu số x bị &#34;kẹp giữa&#34; số y hoặc ngược lại. Ta cũng có thể dễ dàng chứng minh một dãy không tồn tại cặp x, y nào như vậy là một dãy thỏa mãn.&#xA;&#xA;Ví dụ&#xA;1, 2, 1, 3, 1, 4 không thỏa mãn vì số 2 bị kẹp giữa 2 lần số 1.&#xA;&#xA;Bài toán của ta trở thành đi tìm một dãy không có 2 số nào &#34;kẹp&#34; nhau.&#xA;&#xA;Đầu và đuôi&#xA;Xét ví dụ ở trên, ta có thể thấy 2 bị kẹp giữa bởi 2 số 1 ở vị trí 1 và 3. Ta cũng có thể nói 2 bị kẹp giữa bởi 2 số 1 ở 1 và 5.&#xA;&#xA;Giả sử x kẹp giữa y ở 2 vị trí a &lt;= b, ta cũng có thể nói x kẹp ở 2 vị trí first[x] &lt;= a và b &lt;= last[x] (2 lần xuất hiện đầu và cuối của x). Như vậy điều kiện để x kẹp giữa y chỉ là tồn tại y nằm giữa 2 vị trí xa nhau nhất chứa x.&#xA;&#xA;Đi xa hơn, ta có thể thấy xét trên trục 1 chiều, tồn tại cặp x, y kẹp nhau khi và chỉ khi 2 đoạn (first[x], last[x]) và (first[y], last[y]) giao nhau.&#xA;&#xA;Quy hoạch động&#xA;Như vậy, ta chỉ cần tìm 1 tập số sao cho tập (first[x], last[x]) của các số không giao nhau. Đây là bài toán quy hoạch động cơ bản, có thể thực hiện quy hoạch động trong O(N + M) với M là số phần tử khác nhau.&#xA;&#xA;Gọi f[i] là số đoạn thẳng nhiều nhất ta có thể chọn trong khoảng 1..i. Từ đây, ta có 2 lựa chọn:&#xA;Thêm khoảng không, cập nhật f[i] cho f[i + 1].&#xA;Thêm một đoạn (i + 1..j). Ta duyệt tất cả các đoạn thẳng có đầu mút trái là i + 1 và cập nhật f[i] + 1 cho f[j].&#xA;&#xA;Đáp số là f[N].&#xA;&#xA;AVTOGAME&#xA;Có thể xóa 1 đoạn?&#xA;Hiển nhiên các đoạn ta xóa sẽ không giao nhau, nên chỉ có 2 khả năng xảy ra để xóa đoạn [a &lt; b]:&#xA;Nếu S[a] == S[b] ta xóa cả đoạn trong 1 bước.&#xA;Chọn 1 vị trí a &lt; k &lt; b - 1, xóa đoạn a..k rồi xóa đoạn k+1..b.&#xA;&#xA;Dựa vào nhận xét này, ta dễ dàng dựng nên mảng canErasel (có thể xóa đoạn l..r không?) trong O(N^3):&#xA;&#xA;for (int l = 1; l &lt;= N; ++l) {&#xA;  for (int r = l + 1; r &lt;= N; ++r) {&#xA;    if (Sl] == S[r]) canErase[l = 1;&#xA;    for (int k = l + 1; k + 1 &lt; r; ++k) {&#xA;      canErasel = canErasel || (canErasel &amp;&amp; canErasek + 1);&#xA;    }&#xA;  }&#xA;}&#xA;&#xA;Chi phí xóa hết nhỏ nhất&#xA;Thay vì giải bài toán xâu ngắn nhất còn lại, ta sẽ thay đổi bài toán bằng cách cho phép một kiểu xóa nữa: xóa 1 kí tự với chi phí 1. Sau đó ta đi tìm chi phí nhỏ nhất để xóa cả dãy. Ta có thể thấy tính chất các bước xóa rời nhau không thay đổi.&#xA;&#xA;Hiển nhiên chi phí sẽ bằng đáp án, vì ta không bao giờ xóa đơn lẻ 2 kí tự giống nhau.&#xA;&#xA;Để giải được bài toán này, ta cải tiến thuật toán kiểm tra tính xóa được phía trên, thành chi phí nhỏ nhất để xóa đoạn l..r. Hiển nhiên costi = 1 vì chỉ có 1 kí tự. Với đoạn l..r ta có 2 cách xóa:&#xA;Nếu S[l] == S[r] ta xóa cả đoạn với chi phí 0.&#xA;Chọn l &lt;= k &lt; r rồi xóa 2 đoạn l..k và k + 1..r với tổng chi phí costl + costk + 1.&#xA;&#xA;Đáp số là cost1, độ phức tạp là O(N^3).&#xA;&#xA;Các kí tự còn lại&#xA;Để giải được bài toán dãy còn lại dài nhất, ta cần phải thấy tính chất của dãy còn lại. Tính chất khá đơn giản: không tồn tại 2 kí tự giống nhau trong xâu. Như vậy, ta cần nhặt ra 1 tập kí tự khác nhau sao cho các phần ở giữa có thể xóa được.&#xA;&#xA;Điều kiện chỉ có 16 kí tự khác nhau cho ta một gợi ý: sử dụng bitmask để quản lí các kí tự đã lấy.&#xA;&#xA;Quy hoạch động&#xA;Gọi bool fi là tính khả thi của việc chọn ra tập kí tự thỏa mãn mask trong đoạn 1..i và xóa hết các kí tự còn lại, trong đó kí tự cuối ta chọn chính là S[i]. Ta có 2 lựa chọn:&#xA;Chọn cả kí tự Si - 1], với điều kiện S[i - 1] != S[i] và mask có S[i - 1]. Ta lùi về trạng thái f[i - 1].&#xA;Chọn một vị trí j &lt; i và lấy kí tự này là kí tự đứng ngay trước Si]. Điều kiện là S[j] != S[i], mask có S[j] và j + 1..i - 1 xóa được. Ta lùi về trạng thái f[j].&#xA;&#xA;Độ phức tạp sẽ là O(N^2  2^16), chưa thể thỏa mãn bài toán. Ta cần một chút cải tiến để xóa bớt N.&#xA;&#xA;Nhảy, chọn và xóa&#xA;Ta sẽ chỉnh sửa hàm quy hoạch động một chút: xóa bỏ điều kiện Si] là kí tự cuối cùng chọn. Thay vào đó, ta &#34;nhảy&#34; từng bước, chọn hoặc sử dụng duy nhất một phép xóa. Cụ thể, từ trạng thái f[i, ta có:&#xA;Si] là kí tự được chọn. Điều kiện là mask chứa S[i]. Lùi về f[i - 1].&#xA;Si] là kí tự cuối cùng bị xóa. Vậy ta cần một vị trí j &lt; i sao cho S[i] == S[j], và lùi về f[j - 1.&#xA;&#xA;Thoáng qua, vẫn là O(N^2  2^16). Làm sao để cải tiến? Ta thấy, trong trường hợp 2, điều kiện duy nhất là Sj] == S[i], mà chỉ có 16 loại kí tự, vậy ta hoàn toàn có thể lưu lại tất cả các trường hợp f[j - 1 với mỗi lọai S[j] khác nhau.&#xA;&#xA;Gọi gi là tổng kết tất cả các trường hợp fj thỏa mãn Sj + 1] == i. Ta có thể vừa đi vừa cập nhật g[S[i + 1], đồng thời trong trường hợp 2 ta chỉ cần lấy giá trị của gS[i] trong O(1).&#xA;&#xA;Độ phức tạp giảm xuống còn O(N  2^16), thỏa mãn bài toán.&#xA;&#xA;DISKGAME&#xA;Chi phí xoay của 1 đĩa&#xA;Hãy phân tích chi phí xoay của 1 đĩa để có số n ở vị trí p. Hiển nhiên chi phí là min(|x - p|) với x là các vị trí xuất hiện của n trong đĩa.&#xA;&#xA;Thực chất ta chỉ cần xét đến 2 vị trí gần nhất bên trái và bên phải của p. Ta tạm gọi là x và y (để đơn giản ta coi x &lt;= p &lt;= y). Chi phí sẽ là min(p - x, y - p). Dễ dàng nhận thấy p - x là hàm tăng 1 đơn vị, y - p là hàm giảm 1 đơn vị với x &lt;= p &lt;= y. min của 2 hàm này sẽ là &#34;núi&#34; góc 45 độ có chóp ở trung điểm của x và y (hoặc có chóp ngang nếu trung điểm không nguyên).&#xA;&#xA;Nếu ta xét tất cả các cặp vị trí liên tiếp của số, thì chi phí sẽ là nhiều &#34;ngọn núi&#34; như vậy.&#xA;&#xA;Ta có thể thấy chi phí là một hàm như hình dưới, cho dãy 1 2 3 1 2 3 5 1 5 với n = 1. Lưu ý đoạn 8, 9, 1 cũng là 1 &#34;ngọn núi&#34;, vì thực chất đĩa là hình tròn.&#xA;&#xA;Hàm chi phí&#xA;&#xA;Ta có thể cắt hàm thành các đường chéo tăng và giảm 45 độ để đơn giản hóa việc tính toán chi phí cho tất cả các đĩa.&#xA;&#xA;Tổng cộng 1 đĩa sẽ bị cắt thành 2K đường chéo.&#xA;&#xA;Tính tổng chi phí cho mọi đĩa&#xA;Với mỗi vị trí p và một số n, ta cần tính tổng chi phí xoay với mọi đĩa trong O(1). Biết chúng là tổng các đường chéo, làm sao để tính nhanh?&#xA;&#xA;Ta sẽ vận dụng tính chất chúng đều có dạng x + b hoặc -x + b và sử dụng đường quét để tính với mỗi n.&#xA;&#xA;Ta thấy, khi có k đoạn x + b[i], chi phí là kx + sum(b[i]) với bước tăng là k. Vì vậy thực chất với mỗi vị trí ta chỉ cần biết số đoạn tăng và tổng phần hằng số của chúng. Ta hoàn toàn có thể làm điều này khi quét bằng cách xét 2 đầu mút đầu (thêm đoạn) và cuối (xóa đoạn) sau đó xử lí từ trái sang phải.&#xA;&#xA;Điều tương tự cũng đúng với hàm giảm.&#xA;&#xA;int b[N  K + 1]; // tất cả b[i] của các đường tăng&#xA;vectorint add[K + 2], remove[K + 2]; // các mốc thêm xóa&#xA;&#xA;void addSegment(int l, int r, int id) {&#xA;  // thêm đoạn [l..r] = x + b[id]&#xA;  add[l].pushback(id);&#xA;  remove[r + 1].push_back(id);&#xA;}&#xA;&#xA;void scan() {&#xA;  int value = 0, cnt = 0;&#xA;  for (int i = 1; i &lt;= K; ++i) {&#xA;    for (auto p: add[i]) {&#xA;      value += b[p] + i - 1; // giá trị của i trước đó&#xA;      ++cnt;&#xA;    }&#xA;    for (auto p: remove[i]) {&#xA;      value -= b[p] + i - 1;&#xA;      --cnt;&#xA;    }&#xA;    value += cnt;&#xA;    // value là tổng ở vị trí i&#xA;  }&#xA;}&#xA;&#xA;Ta có thể thấy độ phức tạp với mỗi n là O(K + số đoạn của n). Vì thế tổng độ phức tạp là O(K^2 + NK), do có 2NK đoạn tất cả.&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/nki/tag:training" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">training</span></a> <a href="/nki/tag:apio" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">apio</span></a> <a href="/nki/tag:vietnamese" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">vietnamese</span></a> <a href="/nki/tag:th%E1%BA%A7yNgh%C4%A9a" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">thầyNghĩa</span></a></p>

<h1 id="tóm-tắt-đề-bài">Tóm tắt đề bài</h1>

<h2 id="sequence">SEQUENCE</h2>

<p>Cho dãy số <code>A[1..N]</code>. Mỗi lần xóa ta sẽ xóa tất cả các số mang một giá trị <code>x</code> nào đó. Hỏi dãy dài nhất có thể tạo ra được mà không tồn tại <code>i &lt; j &lt; k</code> thỏa mãn <code>A[i] == A[k] &amp;&amp; A[i] != A[j]</code>?</p>

<h4 id="giới-hạn">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 10^5</code>, <code>1 &lt;= A[i] &lt;= 100</code></p>

<h2 id="avtogame">AVTOGAME</h2>

<p>Cho xâu <code>S</code>. Mỗi bước ta có thể chọn một đoạn <code>l &lt; r</code> sao cho <code>S[l] == S[r]</code> và xóa đoạn đó khỏi xâu. Hỏi xâu ngắn nhất và dài nhất có thể tạo được (mà không thể xóa được tiếp) là bao nhiêu?</p>

<h4 id="giới-hạn-1">Giới hạn</h4>

<p>10 test, <code>1 &lt;= |S| &lt;= 100</code>, <code>&#39;a&#39; &lt;= S[i] &lt;= &#39;p&#39;</code></p>

<h2 id="diskgame">DISKGAME</h2>

<p>Cho một đĩa gồm <code>N</code> tầng xoay, mỗi tầng có <code>K</code> nấc xoay như hình dưới.</p>

<p><img src="https://cdn.discordapp.com/attachments/676817846617243658/1108884401510682674/diskgame_exp.png" alt="Một đĩa có 3 tầng, mỗi tầng có 8 nấc"></p>

<p>Mỗi bước ta được xoay 1 tầng sang trái hoặc phải 1 nấc. Hỏi số bước nhỏ nhất để tạo ra 1 cột có các số bằng nhau là bao nhiêu?</p>

<p><img src="https://cdn.discordapp.com/attachments/676817846617243658/1108884401774940251/diskgame_sol.png" alt="Một cách giải hình trên"></p>

<h4 id="giới-hạn-2">Giới hạn</h4>

<p><code>1 &lt;= N, K &lt;= 2000</code></p>



<h1 id="lời-giải">Lời giải</h1>

<h2 id="sequence-1">SEQUENCE</h2>

<h3 id="điều-kiện-của-dãy-số">Điều kiện của dãy số</h3>

<p>Ta có thể thấy, 2 số <code>x</code> và <code>y</code> không được cùng tồn tại trong đáp án nếu số <code>x</code> bị “kẹp giữa” số <code>y</code> hoặc ngược lại. Ta cũng có thể dễ dàng chứng minh một dãy không tồn tại cặp <code>x, y</code> nào như vậy là một dãy thỏa mãn.</p>

<h4 id="ví-dụ">Ví dụ</h4>

<p><code>1, 2, 1, 3, 1, 4</code> không thỏa mãn vì số <code>2</code> bị kẹp giữa 2 lần số <code>1</code>.</p>

<p>Bài toán của ta trở thành đi tìm một dãy không có 2 số nào “kẹp” nhau.</p>

<h3 id="đầu-và-đuôi">Đầu và đuôi</h3>

<p>Xét ví dụ ở trên, ta có thể thấy <code>2</code> bị kẹp giữa bởi 2 số <code>1</code> ở vị trí 1 và 3. Ta cũng có thể nói <code>2</code> bị kẹp giữa bởi 2 số <code>1</code> ở 1 và 5.</p>

<p>Giả sử <code>x</code> kẹp giữa <code>y</code> ở 2 vị trí <code>a &lt;= b</code>, ta cũng có thể nói <code>x</code> kẹp ở 2 vị trí <code>first[x] &lt;= a</code> và <code>b &lt;= last[x]</code> (2 lần xuất hiện đầu và cuối của <code>x</code>). Như vậy điều kiện để <code>x</code> kẹp giữa <code>y</code> chỉ là tồn tại <code>y</code> nằm giữa 2 vị trí xa nhau nhất chứa <code>x</code>.</p>

<p>Đi xa hơn, ta có thể thấy xét trên trục 1 chiều, tồn tại cặp <code>x</code>, <code>y</code> kẹp nhau khi và chỉ khi 2 đoạn <code>(first[x], last[x])</code> và <code>(first[y], last[y])</code> giao nhau.</p>

<h3 id="quy-hoạch-động">Quy hoạch động</h3>

<p>Như vậy, ta chỉ cần tìm 1 tập số sao cho tập <code>(first[x], last[x])</code> của các số không giao nhau. Đây là bài toán quy hoạch động cơ bản, có thể thực hiện quy hoạch động trong <code>O(N + M)</code> với <code>M</code> là số phần tử khác nhau.</p>

<p>Gọi <code>f[i]</code> là số đoạn thẳng nhiều nhất ta có thể chọn trong khoảng <code>1..i</code>. Từ đây, ta có 2 lựa chọn:
– Thêm khoảng không, cập nhật <code>f[i]</code> cho <code>f[i + 1]</code>.
– Thêm một đoạn <code>(i + 1..j)</code>. Ta duyệt tất cả các đoạn thẳng có đầu mút trái là <code>i + 1</code> và cập nhật <code>f[i] + 1</code> cho <code>f[j]</code>.</p>

<p>Đáp số là <code>f[N]</code>.</p>

<h2 id="avtogame-1">AVTOGAME</h2>

<h3 id="có-thể-xóa-1-đoạn">Có thể xóa 1 đoạn?</h3>

<p>Hiển nhiên các đoạn ta xóa sẽ không giao nhau, nên chỉ có 2 khả năng xảy ra để xóa đoạn <code>[a &lt; b]</code>:
– Nếu <code>S[a] == S[b]</code> ta xóa cả đoạn trong 1 bước.
– Chọn 1 vị trí <code>a &lt; k &lt; b - 1</code>, xóa đoạn <code>a..k</code> rồi xóa đoạn <code>k+1..b</code>.</p>

<p>Dựa vào nhận xét này, ta dễ dàng dựng nên mảng <code>canErase[l][r]</code> (có thể xóa đoạn <code>l..r</code> không?) trong <code>O(N^3)</code>:</p>

<pre><code class="language-cpp">for (int l = 1; l &lt;= N; ++l) {
  for (int r = l + 1; r &lt;= N; ++r) {
    if (S[l] == S[r]) canErase[l][r] = 1;
    for (int k = l + 1; k + 1 &lt; r; ++k) {
      canErase[l][r] = canErase[l][r] || (canErase[l][k] &amp;&amp; canErase[k + 1][r]);
    }
  }
}
</code></pre>

<h3 id="chi-phí-xóa-hết-nhỏ-nhất">Chi phí xóa hết nhỏ nhất</h3>

<p>Thay vì giải bài toán xâu ngắn nhất còn lại, ta sẽ thay đổi bài toán bằng cách cho phép một kiểu xóa nữa: xóa <strong>1 kí tự</strong> với chi phí 1. Sau đó ta đi tìm chi phí nhỏ nhất để xóa cả dãy. Ta có thể thấy tính chất các bước xóa rời nhau không thay đổi.</p>

<p>Hiển nhiên chi phí sẽ bằng đáp án, vì ta không bao giờ xóa đơn lẻ 2 kí tự giống nhau.</p>

<p>Để giải được bài toán này, ta cải tiến thuật toán kiểm tra tính xóa được phía trên, thành chi phí nhỏ nhất để xóa đoạn <code>l..r</code>. Hiển nhiên <code>cost[i][i] = 1</code> vì chỉ có 1 kí tự. Với đoạn <code>l..r</code> ta có 2 cách xóa:
– Nếu <code>S[l] == S[r]</code> ta xóa cả đoạn với chi phí 0.
– Chọn <code>l &lt;= k &lt; r</code> rồi xóa 2 đoạn <code>l..k</code> và <code>k + 1..r</code> với tổng chi phí <code>cost[l][k] + cost[k + 1][r]</code>.</p>

<p>Đáp số là <code>cost[1][N]</code>, độ phức tạp là <code>O(N^3)</code>.</p>

<h3 id="các-kí-tự-còn-lại">Các kí tự còn lại</h3>

<p>Để giải được bài toán dãy còn lại dài nhất, ta cần phải thấy tính chất của dãy còn lại. Tính chất khá đơn giản: không tồn tại 2 kí tự giống nhau trong xâu. Như vậy, ta cần nhặt ra 1 tập kí tự khác nhau sao cho các phần ở giữa có thể xóa được.</p>

<p>Điều kiện chỉ có 16 kí tự khác nhau cho ta một gợi ý: sử dụng bitmask để quản lí các kí tự đã lấy.</p>

<h3 id="quy-hoạch-động-1">Quy hoạch động</h3>

<p>Gọi <code>bool f[i][mask]</code> là tính khả thi của việc chọn ra tập kí tự thỏa mãn <code>mask</code> trong đoạn <code>1..i</code> và xóa hết các kí tự còn lại, trong đó kí tự cuối ta chọn chính là <code>S[i]</code>. Ta có 2 lựa chọn:
– Chọn cả kí tự <code>S[i - 1]</code>, với điều kiện <code>S[i - 1] != S[i]</code> và <code>mask</code> có <code>S[i - 1]</code>. Ta lùi về trạng thái <code>f[i - 1][mask ^ S[i]]</code>.
– Chọn một vị trí <code>j &lt; i</code> và lấy kí tự này là kí tự đứng ngay trước <code>S[i]</code>. Điều kiện là <code>S[j] != S[i]</code>, <code>mask</code> có <code>S[j]</code> và <code>j + 1..i - 1</code> xóa được. Ta lùi về trạng thái <code>f[j][mask ^ S[i]]</code>.</p>

<p>Độ phức tạp sẽ là <code>O(N^2 * 2^16)</code>, chưa thể thỏa mãn bài toán. Ta cần một chút cải tiến để xóa bớt <code>N</code>.</p>

<h3 id="nhảy-chọn-và-xóa">Nhảy, chọn và xóa</h3>

<p>Ta sẽ chỉnh sửa hàm quy hoạch động một chút: xóa bỏ điều kiện <code>S[i]</code> là kí tự cuối cùng chọn. Thay vào đó, ta “nhảy” từng bước, chọn hoặc sử dụng duy nhất một phép xóa. Cụ thể, từ trạng thái <code>f[i][mask]</code>, ta có:
– <code>S[i]</code> là kí tự được chọn. Điều kiện là <code>mask</code> chứa <code>S[i]</code>. Lùi về <code>f[i - 1][mask ^ S[i]]</code>.
– <code>S[i]</code> là kí tự cuối cùng bị xóa. Vậy ta cần một vị trí <code>j &lt; i</code> sao cho <code>S[i] == S[j]</code>, và lùi về <code>f[j - 1][mask]</code>.</p>

<p>Thoáng qua, vẫn là <code>O(N^2 * 2^16)</code>. Làm sao để cải tiến? Ta thấy, trong trường hợp 2, điều kiện duy nhất là <code>S[j] == S[i]</code>, mà chỉ có 16 loại kí tự, vậy ta hoàn toàn có thể lưu lại tất cả các trường hợp <code>f[j - 1][mask]</code> với mỗi lọai <code>S[j]</code> khác nhau.</p>

<p>Gọi <code>g[i][mask]</code> là tổng kết tất cả các trường hợp <code>f[j][mask]</code> thỏa mãn <code>S[j + 1] == i</code>. Ta có thể vừa đi vừa cập nhật <code>g[S[i + 1]][mask]</code>, đồng thời trong trường hợp 2 ta chỉ cần lấy giá trị của <code>g[S[i]][mask]</code> trong <code>O(1)</code>.</p>

<p>Độ phức tạp giảm xuống còn <code>O(N * 2^16)</code>, thỏa mãn bài toán.</p>

<h2 id="diskgame-1">DISKGAME</h2>

<h3 id="chi-phí-xoay-của-1-đĩa">Chi phí xoay của 1 đĩa</h3>

<p>Hãy phân tích chi phí xoay của 1 đĩa để có số <code>n</code> ở vị trí <code>p</code>. Hiển nhiên chi phí là <code>min(|x - p|)</code> với <code>x</code> là các vị trí xuất hiện của <code>n</code> trong đĩa.</p>

<p>Thực chất ta chỉ cần xét đến 2 vị trí gần nhất bên trái và bên phải của <code>p</code>. Ta tạm gọi là <code>x</code> và <code>y</code> (để đơn giản ta coi <code>x &lt;= p &lt;= y</code>). Chi phí sẽ là <code>min(p - x, y - p)</code>. Dễ dàng nhận thấy <code>p - x</code> là hàm tăng 1 đơn vị, <code>y - p</code> là hàm giảm 1 đơn vị với <code>x &lt;= p &lt;= y</code>. min của 2 hàm này sẽ là “núi” góc 45 độ có chóp ở trung điểm của <code>x</code> và <code>y</code> (hoặc có chóp ngang nếu trung điểm không nguyên).</p>

<p>Nếu ta xét tất cả các cặp vị trí liên tiếp của số, thì chi phí sẽ là nhiều “ngọn núi” như vậy.</p>

<p>Ta có thể thấy chi phí là một hàm như hình dưới, cho dãy <code>1 2 3 1 2 3 5 1 5</code> với <code>n = 1</code>. Lưu ý đoạn <code>8, 9, 1</code> cũng là 1 “ngọn núi”, vì thực chất đĩa là hình tròn.</p>

<p><img src="https://cdn.discordapp.com/attachments/676817846617243658/1108884473422041138/diskgame_func.png" alt="Hàm chi phí"></p>

<p>Ta có thể cắt hàm thành các đường chéo tăng và giảm 45 độ để đơn giản hóa việc tính toán chi phí cho tất cả các đĩa.</p>

<p>Tổng cộng 1 đĩa sẽ bị cắt thành <code>2K</code> đường chéo.</p>

<h3 id="tính-tổng-chi-phí-cho-mọi-đĩa">Tính tổng chi phí cho mọi đĩa</h3>

<p>Với mỗi vị trí <code>p</code> và một số <code>n</code>, ta cần tính tổng chi phí xoay với mọi đĩa trong <code>O(1)</code>. Biết chúng là tổng các đường chéo, làm sao để tính nhanh?</p>

<p>Ta sẽ vận dụng tính chất chúng đều có dạng <code>x + b</code> hoặc <code>-x + b</code> và sử dụng đường quét để tính với mỗi <code>n</code>.</p>

<p>Ta thấy, khi có <code>k</code> đoạn <code>x + b[i]</code>, chi phí là <code>kx + sum(b[i])</code> với bước tăng là <code>k</code>. Vì vậy thực chất với mỗi vị trí ta chỉ cần biết số đoạn tăng và tổng phần hằng số của chúng. Ta hoàn toàn có thể làm điều này khi quét bằng cách xét 2 đầu mút đầu (thêm đoạn) và cuối (xóa đoạn) sau đó xử lí từ trái sang phải.</p>

<p>Điều tương tự cũng đúng với hàm giảm.</p>

<pre><code class="language-cpp">int b[N * K + 1]; // tất cả b[i] của các đường tăng
vector&lt;int&gt; add[K + 2], remove[K + 2]; // các mốc thêm xóa

void addSegment(int l, int r, int id) {
  // thêm đoạn [l..r] = x + b[id]
  add[l].push_back(id);
  remove[r + 1].push_back(id);
}

void scan() {
  int value = 0, cnt = 0;
  for (int i = 1; i &lt;= K; ++i) {
    for (auto p: add[i]) {
      value += b[p] + i - 1; // giá trị của i trước đó
      ++cnt;
    }
    for (auto p: remove[i]) {
      value -= b[p] + i - 1;
      --cnt;
    }
    value += cnt;
    // value là tổng ở vị trí i
  }
}
</code></pre>

<p>Ta có thể thấy độ phức tạp với mỗi <code>n</code> là <code>O(K + số đoạn của n)</code>. Vì thế tổng độ phức tạp là <code>O(K^2 + NK)</code>, do có <code>2NK</code> đoạn tất cả.</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/2017-04-21-training</guid>
      <pubDate>Sun, 23 Apr 2017 06:11:00 +0000</pubDate>
    </item>
    <item>
      <title>2017/04/20 Training</title>
      <link>https://blog.dtth.ch/nki/2017-04-20-training</link>
      <description>&lt;![CDATA[#training #apio #vietnamese #thầyĐông&#xA;&#xA;Lưu ý: Mình đã tóm tắt đề bài ở trên, ai không muốn bị spoil thì đừng&#xA;kéo xuống lời giải vội.&#xA;&#xA;Hôm nay có 5 bài của thầy Đông. Do mình không được nghe thầy chữa buổi chiều nên solution là của mình, mặc dù 99% là đúng nhưng không đảm bảo.&#xA;Thực chất bài không phải là khó quá.&#xA;&#xA;!--more--&#xA;&#xA;Tóm tắt đề bài&#xA;ACM&#xA;Có N đội, mỗi đội có 11 chỉ số Ai. Chọn 3 đội sao cho sumi = 1..11[i])] (tổng của max chỉ số từng loại của 3 đội) là lớn nhất.&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 30000&#xA;DOMINO (Bài toán thứ nhất)&#xA;Cho một bảng M x N với K ô cấm. Điền 0 hoặc 1 vào các ô không bị cấm sao cho với ô (i, j):&#xA;Nếu i + j lẻ, thì ô (i, j) không nhỏ hơn các ô không bị cấm xung quanh.&#xA;Nếu i + j chẵn, thì (i, j) không lớn hơn các ô không bị cấm xung quanh.&#xA;&#xA;Giới hạn&#xA;1 &lt;= M, N &lt;= 16&#xA;DOMINO (Bài toán thứ hai)&#xA;Lần này không có ô cấm. Điều kiện như bài toán thứ nhất.&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 8, 1 &lt;= M &lt;= 10^6&#xA;GAMES&#xA;Cho một dãy bit N phần tử chưa xác định và M điều kiện có dạng xor của A[l..r] là 0 hay 1. Tìm vị trí x đầu tiên mà điều kiện x mâu thuẫn với các điều kiện trên.&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 10^9, 1 &lt;= M &lt;= 10^5&#xA;HANOI&#xA;Cho thuật toán giải bài toán tháp Hà Nội:&#xA;def HanoiTower(height, From, Temp, To):&#xA;  if height == 1:&#xA;    # Move one from From to To&#xA;    return&#xA;  HanoiTower(height - 1, From, To, Temp)&#xA;  HanoiTower(1, From, Temp, To)&#xA;  HanoiTower(height - 1, Temp, From, To)&#xA;&#xA;Call the function&#xA;HanoiTower(N, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;)&#xA;Tìm trạng thái của 3 tháp sau P lần gọi hàm.&#xA;Cho trạng thái của 3 tháp, tính số bước đã gọi hàm P, hoặc in -1 nếu không tồn tại trạng thái đó khi giải.&#xA;&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 100. Lưu ý P có thể là số lớn.&#xA;WG&#xA;Cho một xâu P và một tập xâu S[1..N]. Dựng xâu T như sau:&#xA;Đầu tiên chọn một xâu trong tập S[] và thêm vào T&#xA;Sau đó, tìm xâu Si] bất kì thỏa mãn S[0] == T.back() rồi thêm S[i vào.&#xA;&#xA;Hỏi xâu T ngắn nhất chứa dãy con không liên tiếp P là gì?&#xA;&#xA;Giới hạn&#xA;1 &lt;= |P| &lt;= 250, 1 &lt;= |S[i]| &lt;= 10, 1 &lt;= N &lt;= 1000&#xA;&#xA;Lời giải&#xA;ACM&#xA;Tóm tắt đề bài&#xA;Có N đội, mỗi đội có 11 chỉ số Ai. Chọn 3 đội sao cho sumi = 1..11[i])] (tổng của max chỉ số từng loại của 3 đội) là lớn nhất.&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 30000&#xA;&#34;Phức tạp hóa&#34; bài toán&#xA;Rất khó để thực hiện việc chọn nếu mình sử dụng việc lấy max của từng chỉ số. Vì thế ta có thể thay đổi bài toán thành chọn 3 đội rồi mỗi chỉ số lấy của một đội. Ta có thể thấy khi có quản lí đơn giản hơn: chỉ cần mỗi đội một bitmask lựa chọn chỉ số.&#xA;&#xA;Hiển nhiên khi đã xét tất cả trường hợp thì trường hợp tốt nhất luôn là lấy max.&#xA;&#xA;Ghép bitmask&#xA;Giả sử ta đã chọn 3 đội i, j và k. Ta sẽ gán lần lượt 3 mask x, y, z cho 3 đội này. Các mask sẽ thỏa mãn:&#xA;Đôi một rời rạc (x &amp; y == 0, y &amp; z == 0, z &amp; x == 0)&#xA;Ghép lại thì có đầy đủ (x | y | z == (1 &lt;&lt; 11) - 1)&#xA;Tổng chỉ số tương ứng lớn nhất.&#xA;&#xA;Ta có thể thấy, thực chất ta không cần quan tâm các đội được chọn là đội nào. Với mask x được chọn trước, ta chỉ cần tính xem trong các đội thì đội nào có tổng tương ứng mask x là lớn nhất.&#xA;Việc 2 mask x và y bị chọn trùng đội không quan trọng: Ta có thể coi như khi đó có 1 người được chọn với mask 0.&#xA;&#xA;Ta có thể tính trước max[x] với mask x trong O(N  2^11).&#xA;&#xA;Chọn 3 phần tử&#xA;Trước tiên, ta có thể thấy nếu chỉ chọn 2 phần tử, ta có thể for tất cả cặp mask, kiểm tra trong O((2^11)^2). Hiển nhiên do 2 mask đều chỉ có 11 bit nên khi or lại với nhau (ghép bộ) thì mask mới vẫn chỉ có 11 bit. Vậy để chọn 3 phần tử, ta có thể tiếp tục ghép cặp tập đã or với tập max[x].&#xA;&#xA;int two[1 &lt;&lt; 11];&#xA;int three[1 &lt;&lt; 11];&#xA;&#xA;for (int i = 0; i &lt; (1 &lt;&lt; 11); ++i) {&#xA;  for (int j = 0; j &lt; (1 &lt;&lt; 11); ++j) {&#xA;    if (!(i &amp; j)) // không có phần tử trùng&#xA;      two[i | j] = max(two[i | j], max[i] + max[j]);&#xA;  }&#xA;}&#xA;for (int i = 0; i &lt; (1 &lt;&lt; 11); ++i) {&#xA;  for (int j = 0; j &lt; (1 &lt;&lt; 11); ++j) {&#xA;    if (!(i &amp; j)) // không có phần tử trùng&#xA;      three[i | j] = max(three[i | j], two[i] + max[j]);&#xA;  }&#xA;}&#xA;&#xA;Đáp số chính là three[(1 &lt;&lt; 11) - 1]. Độ phức tạp sẽ là O((2 ^ 11) ^ 2).&#xA;&#xA;DOMINO (Bài toán thứ nhất)&#xA;Tóm tắt đề bài&#xA;Cho một bảng M x N với K ô cấm. Điền 0 hoặc 1 vào các ô không bị cấm sao cho với ô (i, j):&#xA;Nếu i + j lẻ, thì ô (i, j) không nhỏ hơn các ô không bị cấm xung quanh.&#xA;Nếu i + j chẵn, thì (i, j) không lớn hơn các ô không bị cấm xung quanh.&#xA;&#xA;Giới hạn&#xA;1 &lt;= M, N &lt;= 16&#xA;Quy hoạch động&#xA;Thực chất đây là một bài toán quy hoạch động bitmask cơ bản. Nhận xét rằng với mỗi ô ta chỉ cần để ý bit ô bên trái và bên trên nó để có thể xét điều kiện thỏa mãn.&#xA;&#xA;Khi quy hoạch động ta đi từng ô theo từng cột, trên xuống dưới trái qua phải. Gọi fi[mask] là số cách lát kể từ ô (i, j) đến cuối cùng, với mask là trạng thái N ô cuối cùng đã đến trước (i, j) (tức các ô (i - 1, j), (i - 1, j + 1), ..., (i, j - 1)). Xem hính dưới:&#xA;&#xA;Bài Domino&#xA;&#xA;Hình thể hiện trạng thái khi đã đến ô (i, j). Ô màu xanh lá là các ô đã lát, ô màu vàng thể hiện mask đang bị quản lí bởi bit tương ứng trong mask, ô màu xanh dương thể hiện ô sắp điền, ô màu đỏ thể hiện các ô chưa lát.&#xA;&#xA;Để chuyển trạng thái ta xác định bit của ô (i, j), nếu nó thỏa mãn điều kiện với ô trái và trên thì gọi đến trạng thái tiếp theo (fimask mới] hoặc f[i + 1[mask mới] nếu đó là ô cuối của cột).&#xA;&#xA;Chuyển mask như nào?&#xA;Ta để ý trên hình, bit 3, kể từ ô tiếp theo, không cần biết đến nữa. Ta có thể xóa bit này và đẩy lên, cho bit của (i, j) vào cuối. Như vậy trạng thái của mình luôn có N bit.&#xA;&#xA;Độ phức tạp là O(N  M  2^N).&#xA;&#xA;Cài đặt như nào?&#xA;Nên gọi đệ quy có nhớ.&#xA;&#xA;int f20[1 &lt;&lt; 16];&#xA;bool visited20[1 &lt;&lt; 16];&#xA;&#xA;int cal(int i, int j, int mask) {&#xA;  if (i   M) return 1; // trường hợp biên&#xA;  if (visitedi[mask]) // đã tính&#xA;    return fi[mask];&#xA;  visitedi[mask] = 1;&#xA;  for (int ch = 0; ch &lt; 2; ++ch) {&#xA;    if (/kiểm tra điều kiện đặt bit ch ở (i, j)/) {&#xA;      fi[mask] += cal(i + (j == N), j % N + 1, (mask &lt;&lt; 1) &amp; ((1 &lt;&lt; N) - 1) + ch);&#xA;    }&#xA;  }&#xA;  return fi[mask];&#xA;}&#xA;&#xA;// x &amp; ((1 &lt;&lt; N) - 1) để lấy x % (1 &lt;&lt; N), N bit cuối của x.&#xA;// (x &lt;&lt; 1) == x  2, đẩy các bit sang phải 1 đơn vị.&#xA;&#xA;int ans = cal(1, 1, 0);&#xA;&#xA;DOMINO (Bài toán thứ hai)&#xA;Lần này không có ô cấm. Điều kiện như bài toán thứ nhất.&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 8, 1 &lt;= M &lt;= 10^6&#xA;&#xA;Nhân ma trận&#xA;Lần này không có ô cấm, nên với mỗi hàng ta chỉ cần quan tâm mask của nó là gì. Từ đây ta có thể nghĩ đến việc nhân ma trận.&#xA;&#xA;Giới hạn N nhỏ, M lớn cũng mang đến cho ta gợi ý này.&#xA;&#xA;Số trạng thái&#xA;O((2 ^ N)^3  log(M)) chưa thể thỏa mãn bài toán. Ta phân tích thêm một chút: với mỗi cột, ta có thể loại ra các trạng thái không thỏa mãn các điều kiện giữa 2 ô liên tiếp trên cùng cột.&#xA;&#xA;Việc thử nghiệm cho thấy với N = 8 cũng chỉ có 55 trạng thái, có thể nhân ma trận.&#xA;&#xA;Điều kiện theo i + j&#xA;Khi chuyển từ cột 2i sang cột 2i + 1, điều kiện bị thay đổi: thứ tự các ô trong cột đang từ lẻ, chẵn, lẻ, ... thành chẵn, lẻ, chẵn,... Điều này làm cho việc chuyển trạng thái không thể thực hiện đơn thuần.&#xA;&#xA;Ta có thể sửa điều này bằng cách thêm 1 bit cho trạng thái của cột, chỉ xem đây là trạng thái cho cột lẻ hay chẵn.&#xA;&#xA;Bảng chuyển đổi của mình sẽ chỉ cho phép chuyển từ cột lẻ sang chẵn và ngược lại.&#xA;&#xA;Để đơn giản từ giờ ta gọi số trạng thái là P (P &lt;= 110).&#xA;&#xA;Ma trận gốc và đáp số&#xA;Hiển nhiên ta trận gốc là một ma trận 1 x P, trong đó các ô thể hiện trạng thái cột lẻ sẽ là 1. Ta nhân ma trận gốc với bảng chuyển đổi đã lũy thừa M - 1, nhận được ma trận đáp số 1 x P. Đáp án chính là tổng các phần tử trong ma trận.&#xA;&#xA;Độ phức tạp là O(P ^ 3  log(M)).&#xA;&#xA;GAMES&#xA;Tóm tắt đề bài&#xA;Cho một dãy bit N phần tử chưa xác định và M điều kiện có dạng xor của A[l..r] là 0 hay 1. Tìm vị trí x đầu tiên mà điều kiện x mâu thuẫn với các điều kiện trên.&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 10^9, 1 &lt;= M &lt;= 10^5&#xA;Chặt nhị phân&#xA;Để tìm vị trí đầu tiên mâu thuẫn, ta chặt nhị phân x để tìm vị trí xa nhất mà vẫn tồn tại một dãy thỏa mãn các điều kiện từ 1 đến x.&#xA;&#xA;Bài toán trở thành kiểm tra xem có một dãy tồn tại không.&#xA;Tính chất mảng dồn&#xA;Nếu ta xét mảng dồn S[1..N], thì điều kiện tổng xor l..r bằng 0 hay 1 tương đương với S[l - 1] với S[r] bằng nhau hay khác nhau.&#xA;&#xA;Ngoài ra, S[i] có thể nhận được bất kí giá trị nào không phụ thuộc vào S[i - 1] nên ta có thể thoải mái gán một giá trị bất kì, nhưng chỉ một mà thôi.&#xA;&#xA;Bài toán trở thành, liệu có thể gán dãy S[1..N] thỏa mãn các điều kiện các nhau không?&#xA;&#xA;2 giá trị cho 1 biến&#xA;Ta có thể coi mảng S[] như một đồ thị N đỉnh. Gộp các đỉnh cùng giá trị, ta thấy việc gán giá trị 0-1 cho các đỉnh còn lại giống như tô màu 2 phía.&#xA;&#xA;Như vậy, ta có thể kiểm tra xem đồ thị có phải 2 phía không.&#xA;&#xA;Độ phức tạp sẽ là O(M + N).&#xA;&#xA;Giảm số lượng đỉnh&#xA;Có tận 10^9 đỉnh, tuy nhiên chỉ có 10^5 cạnh. Vì thế chỉ có tối đa 2  10^5 đỉnh có bậc khác 0, ta chỉ cần quan tâm tới các đỉnh này.&#xA;&#xA;Độ phức tạp chỉ còn O(M), mang lại thuật toán O(M  log(M)).&#xA;&#xA;HANOI&#xA;Tóm tắt đề bài&#xA;Cho thuật toán giải bài toán tháp Hà Nội:&#xA;def HanoiTower(height, From, Temp, To):&#xA;  if height == 1:&#xA;    # Move one from From to To&#xA;    return&#xA;  HanoiTower(height - 1, From, To, Temp)&#xA;  HanoiTower(1, From, Temp, To)&#xA;  HanoiTower(height - 1, Temp, From, To)&#xA;&#xA;Call the function&#xA;HanoiTower(N, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;)&#xA;Tìm trạng thái của 3 tháp sau P lần gọi hàm.&#xA;Cho trạng thái của 3 tháp, tính số bước đã gọi hàm P, hoặc in -1 nếu không tồn tại trạng thái đó khi giải.&#xA;&#xA;Giới hạn&#xA;1 &lt;= N &lt;= 100. Lưu ý P có thể là số lớn.&#xA;Các bước của thuật toán&#xA;Ta có thể tóm tắt thuật toán trong 3 bước:&#xA; Chuyển tháp N - 1 từ A sang B dùng C làm đệm&#xA; Chuyển đĩa N từ A sang C&#xA; Chuyển tháp N - 1 từ B sang C dùng A làm đệm&#xA;&#xA;Từ thuật toán, ta có thể xác định mình đang ở bước nào bằng cách xét vị trí của đĩa N.&#xA;Nếu N còn ở A thì ta ở bước 1.&#xA;Nếu không ta ở bước 2 hoặc 3.&#xA;&#xA;Sau khi xác định được vị trí của N, ta có thể bỏ nó đi và đệ quy xuống bước dưới, coi như ta đang giải bài toán chuyển tháp N - 1.&#xA;&#xA;Tìm trạng thái từ P&#xA;Ta biết để chuyển tháp x sẽ mất 2^x - 1 bước, nên khi xét vị trí đĩa N ta có thể xác định xem ta đang ở bước mấy của việc chuyển tháp N:&#xA;Nếu P &lt; 2^x thì ta đang ở bước 1.&#xA;Nếu P = 2^x thì ta đang ở bước 2.&#xA;Nếu P   2^x thì ta đang ở lượt P - 2^x của bước 3.&#xA;&#xA;Tùy theo bước ta xác định vị trí của đĩa N rồi đệ quy xuống bước tương ứng. Độ phức tạp là O(N). Code khá giống bò trên BST.&#xA;&#xA;Tìm P từ trạng thái&#xA;Việc tìm P không khác gì tìm trạng thái. Khi xét tháp N, ta kiểm tra xem mình ở bước nào tùy theo vị trí của đĩa N:&#xA;Nếu N ở A, thì ta ở bước 1. Đệ quy vào bước 1.&#xA;Nếu N ở C, ta ở bước 2 hoặc 3. Cộng P thêm 2^x (cho bước 1+2) rồi đệ quy vào 3.&#xA;&#xA;Độ phức tạp cũng là O(N).&#xA;&#xA;WG&#xA;Tóm tắt đề bài&#xA;Cho một xâu P và một tập xâu S[1..N]. Dựng xâu T như sau:&#xA;Đầu tiên chọn một xâu trong tập S[] và thêm vào T&#xA;Sau đó, tìm xâu Si] bất kì thỏa mãn S[0] == T.back() rồi thêm S[i vào.&#xA;&#xA;Hỏi xâu T ngắn nhất chứa dãy con không liên tiếp P là gì?&#xA;&#xA;Giới hạn&#xA;1 &lt;= |P| &lt;= 250, 1 &lt;= |S[i]| &lt;= 10, 1 &lt;= N &lt;= 1000&#xA;&#xA;Tham lam dựng xâu&#xA;Khi đã có xâu T có thể lấy ra được dãy con là prefix x của P, ta có thể xác định số lượng kí tự ghép thêm khi thêm xâu S[i] bằng cách đi từ trái sang phải, tham lam kí tự tiếp theo cần ghép.&#xA;&#xA;Từ đó ta tính trước được mảng nxi, khi thêm xâu i với j kí tự đã ghép thì trạng thái mới là bao nhiêu. Độ phức tạp sẽ là O(N  |P|  |S[i]|).&#xA;&#xA;Quy hoạch động&#xA;Ta có thể quy hoạch động fi là độ dài xâu T ngắn nhất sao cho xâu cuối cùng là i và đã ghép được j kí tự đầu tiên của P. Ta chọn thêm một xâu Sk] mới và chuyển trạng thái sang f[kj]]. Điều kiện là S[i].back() == S[k và nxk != j.&#xA;&#xA;Độ phức tạp sẽ là O(N^2  |P|), chưa thỏa mãn bài toán.&#xA;&#xA;Kí tự cuối&#xA;Thực chất ta không cần lưu chiều i là xâu cuối cùng, vì ta chỉ cần quan tâm đến kí tự cuối cùng của T, nên thay vào đó ta có thể chỉ lưu i là kí tự cuối cùng.&#xA;&#xA;Độ phức tạp giảm xuống còn O(26  |P| * N), thỏa mãn bài toán.&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/nki/tag:training" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">training</span></a> <a href="/nki/tag:apio" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">apio</span></a> <a href="/nki/tag:vietnamese" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">vietnamese</span></a> <a href="/nki/tag:th%E1%BA%A7y%C4%90%C3%B4ng" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">thầyĐông</span></a></p>

<p><strong>Lưu ý</strong>: Mình đã tóm tắt đề bài ở trên, ai không muốn bị spoil thì đừng
kéo xuống lời giải vội.</p>

<p>Hôm nay có 5 bài của thầy Đông. Do mình không được nghe thầy chữa buổi chiều nên solution là của mình, mặc dù 99% là đúng nhưng không đảm bảo.
Thực chất bài không phải là khó quá.</p>



<h1 id="tóm-tắt-đề-bài">Tóm tắt đề bài</h1>

<h2 id="acm">ACM</h2>

<p>Có <code>N</code> đội, mỗi đội có 11 chỉ số <code>A[i][1..11]</code>. Chọn 3 đội sao cho <code>sum[i = 1..11][max(A[x, y, z][i])]</code> (tổng của max chỉ số từng loại của 3 đội) là lớn nhất.</p>

<h4 id="giới-hạn">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 30000</code></p>

<h2 id="domino-bài-toán-thứ-nhất">DOMINO (Bài toán thứ nhất)</h2>

<p>Cho một bảng <code>M x N</code> với <code>K</code> ô cấm. Điền 0 hoặc 1 vào các ô không bị cấm sao cho với ô <code>(i, j)</code>:
– Nếu <code>i + j</code> lẻ, thì ô <code>(i, j)</code> không nhỏ hơn các ô không bị cấm xung quanh.
– Nếu <code>i + j</code> chẵn, thì <code>(i, j)</code> không lớn hơn các ô không bị cấm xung quanh.</p>

<h4 id="giới-hạn-1">Giới hạn</h4>

<p><code>1 &lt;= M, N &lt;= 16</code></p>

<h2 id="domino-bài-toán-thứ-hai">DOMINO (Bài toán thứ hai)</h2>

<p>Lần này không có ô cấm. Điều kiện như bài toán thứ nhất.</p>

<h4 id="giới-hạn-2">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 8</code>, <code>1 &lt;= M &lt;= 10^6</code></p>

<h2 id="games">GAMES</h2>

<p>Cho một dãy bit <code>N</code> phần tử chưa xác định và <code>M</code> điều kiện có dạng xor của <code>A[l..r]</code> là 0 hay 1. Tìm vị trí <code>x</code> đầu tiên mà điều kiện <code>x</code> mâu thuẫn với các điều kiện trên.</p>

<h4 id="giới-hạn-3">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 10^9</code>, <code>1 &lt;= M &lt;= 10^5</code></p>

<h2 id="hanoi">HANOI</h2>

<p>Cho thuật toán giải bài toán tháp Hà Nội:</p>

<pre><code class="language-python">def HanoiTower(height, From, Temp, To):
  if height == 1:
    # Move one from `From` to `To`
    return
  HanoiTower(height - 1, From, To, Temp)
  HanoiTower(1, From, Temp, To)
  HanoiTower(height - 1, Temp, From, To)

# Call the function
HanoiTower(N, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;)
</code></pre>
<ul><li>Tìm trạng thái của 3 tháp sau <code>P</code> lần gọi hàm.</li>
<li>Cho trạng thái của 3 tháp, tính số bước đã gọi hàm <code>P</code>, hoặc in <code>-1</code> nếu không tồn tại trạng thái đó khi giải.</li></ul>

<h4 id="giới-hạn-4">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 100</code>. Lưu ý <code>P</code> có thể là số lớn.</p>

<h2 id="wg">WG</h2>

<p>Cho một xâu <code>P</code> và một tập xâu <code>S[1..N]</code>. Dựng xâu <code>T</code> như sau:
– Đầu tiên chọn một xâu trong tập <code>S[]</code> và thêm vào <code>T</code>
– Sau đó, tìm xâu <code>S[i]</code> bất kì thỏa mãn <code>S[0] == T.back()</code> rồi thêm <code>S[i][1..]</code> vào.</p>

<p>Hỏi xâu <code>T</code> ngắn nhất chứa dãy con không liên tiếp <code>P</code> là gì?</p>

<h4 id="giới-hạn-5">Giới hạn</h4>

<p><code>1 &lt;= |P| &lt;= 250, 1 &lt;= |S[i]| &lt;= 10, 1 &lt;= N &lt;= 1000</code></p>

<h1 id="lời-giải">Lời giải</h1>

<h2 id="acm-1">ACM</h2>

<h3 id="tóm-tắt-đề-bài-1">Tóm tắt đề bài</h3>

<p>Có <code>N</code> đội, mỗi đội có 11 chỉ số <code>A[i][1..11]</code>. Chọn 3 đội sao cho <code>sum[i = 1..11][max(A[x, y, z][i])]</code> (tổng của max chỉ số từng loại của 3 đội) là lớn nhất.</p>

<h4 id="giới-hạn-6">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 30000</code></p>

<h3 id="phức-tạp-hóa-bài-toán">“Phức tạp hóa” bài toán</h3>

<p>Rất khó để thực hiện việc chọn nếu mình sử dụng việc lấy max của từng chỉ số. Vì thế ta có thể thay đổi bài toán thành <strong>chọn 3 đội rồi mỗi chỉ số lấy của một đội</strong>. Ta có thể thấy khi có quản lí đơn giản hơn: chỉ cần mỗi đội một bitmask lựa chọn chỉ số.</p>

<p>Hiển nhiên khi đã xét tất cả trường hợp thì trường hợp tốt nhất luôn là lấy max.</p>

<h3 id="ghép-bitmask">Ghép bitmask</h3>

<p>Giả sử ta đã chọn 3 đội <code>i</code>, <code>j</code> và <code>k</code>. Ta sẽ gán lần lượt 3 mask <code>x</code>, <code>y</code>, <code>z</code> cho 3 đội này. Các mask sẽ thỏa mãn:
– Đôi một rời rạc (<code>x &amp; y == 0</code>, <code>y &amp; z == 0</code>, <code>z &amp; x == 0</code>)
– Ghép lại thì có đầy đủ (<code>x | y | z == (1 &lt;&lt; 11) - 1</code>)
– Tổng chỉ số tương ứng lớn nhất.</p>

<p>Ta có thể thấy, thực chất ta không cần quan tâm các đội được chọn là đội nào. Với mask <code>x</code> được chọn trước, ta chỉ cần tính xem trong các đội thì đội nào có tổng tương ứng mask <code>x</code> là lớn nhất.
Việc 2 mask <code>x</code> và <code>y</code> bị chọn trùng đội không quan trọng: Ta có thể coi như khi đó có 1 người được chọn với mask <code>0</code>.</p>

<p>Ta có thể tính trước <code>max[x]</code> với mask <code>x</code> trong <code>O(N * 2^11)</code>.</p>

<h3 id="chọn-3-phần-tử">Chọn 3 phần tử</h3>

<p>Trước tiên, ta có thể thấy nếu chỉ chọn 2 phần tử, ta có thể for tất cả cặp mask, kiểm tra trong <code>O((2^11)^2)</code>. Hiển nhiên do 2 mask đều chỉ có 11 bit nên khi <code>or</code> lại với nhau (ghép bộ) thì mask mới vẫn chỉ có 11 bit. Vậy để chọn 3 phần tử, ta có thể tiếp tục ghép cặp tập đã or với tập <code>max[x]</code>.</p>

<pre><code class="language-cpp">int two[1 &lt;&lt; 11];
int three[1 &lt;&lt; 11];

for (int i = 0; i &lt; (1 &lt;&lt; 11); ++i) {
  for (int j = 0; j &lt; (1 &lt;&lt; 11); ++j) {
    if (!(i &amp; j)) // không có phần tử trùng
      two[i | j] = max(two[i | j], max[i] + max[j]);
  }
}
for (int i = 0; i &lt; (1 &lt;&lt; 11); ++i) {
  for (int j = 0; j &lt; (1 &lt;&lt; 11); ++j) {
    if (!(i &amp; j)) // không có phần tử trùng
      three[i | j] = max(three[i | j], two[i] + max[j]);
  }
}
</code></pre>

<p>Đáp số chính là <code>three[(1 &lt;&lt; 11) - 1]</code>. Độ phức tạp sẽ là <code>O((2 ^ 11) ^ 2)</code>.</p>

<h2 id="domino-bài-toán-thứ-nhất-1">DOMINO (Bài toán thứ nhất)</h2>

<h3 id="tóm-tắt-đề-bài-2">Tóm tắt đề bài</h3>

<p>Cho một bảng <code>M x N</code> với <code>K</code> ô cấm. Điền 0 hoặc 1 vào các ô không bị cấm sao cho với ô <code>(i, j)</code>:
– Nếu <code>i + j</code> lẻ, thì ô <code>(i, j)</code> không nhỏ hơn các ô không bị cấm xung quanh.
– Nếu <code>i + j</code> chẵn, thì <code>(i, j)</code> không lớn hơn các ô không bị cấm xung quanh.</p>

<h4 id="giới-hạn-7">Giới hạn</h4>

<p><code>1 &lt;= M, N &lt;= 16</code></p>

<h3 id="quy-hoạch-động">Quy hoạch động</h3>

<p>Thực chất đây là một bài toán quy hoạch động bitmask cơ bản. Nhận xét rằng với mỗi ô ta chỉ cần để ý bit ô bên trái và bên trên nó để có thể xét điều kiện thỏa mãn.</p>

<p>Khi quy hoạch động ta đi từng ô theo từng cột, trên xuống dưới trái qua phải. Gọi <code>f[i][j][mask]</code> là số cách lát kể từ ô <code>(i, j)</code> đến cuối cùng, với <code>mask</code> là trạng thái <code>N</code> ô cuối cùng đã đến trước <code>(i, j)</code> (tức các ô <code>(i - 1, j)</code>, <code>(i - 1, j + 1)</code>, ..., <code>(i, j - 1)</code>). Xem hính dưới:</p>

<p><img src="https://cdn.discordapp.com/attachments/676817846617243658/1108885514007879702/20170420_domino.png" alt="Bài Domino"></p>

<p>Hình thể hiện trạng thái khi đã đến ô <code>(i, j)</code>. Ô màu xanh lá là các ô đã lát, ô màu vàng thể hiện mask đang bị quản lí bởi bit tương ứng trong mask, ô màu xanh dương thể hiện ô sắp điền, ô màu đỏ thể hiện các ô chưa lát.</p>

<p>Để chuyển trạng thái ta xác định bit của ô <code>(i, j)</code>, nếu nó thỏa mãn điều kiện với ô trái và trên thì gọi đến trạng thái tiếp theo (<code>f[i][j + 1][mask mới]</code> hoặc <code>f[i + 1][1][mask mới]</code> nếu đó là ô cuối của cột).</p>

<h4 id="chuyển-mask-như-nào">Chuyển mask như nào?</h4>

<p>Ta để ý trên hình, bit <code>3</code>, kể từ ô tiếp theo, không cần biết đến nữa. Ta có thể xóa bit này và đẩy lên, cho bit của <code>(i, j)</code> vào cuối. Như vậy trạng thái của mình luôn có <code>N</code> bit.</p>

<p>Độ phức tạp là <code>O(N * M * 2^N)</code>.</p>

<h3 id="cài-đặt-như-nào">Cài đặt như nào?</h3>

<p>Nên gọi đệ quy có nhớ.</p>

<pre><code class="language-cpp">int f[20][20][1 &lt;&lt; 16];
bool visited[20][20][1 &lt;&lt; 16];

int cal(int i, int j, int mask) {
  if (i &gt; M) return 1; // trường hợp biên
  if (visited[i][j][mask]) // đã tính
    return f[i][j][mask];
  visited[i][j][mask] = 1;
  for (int ch = 0; ch &lt; 2; ++ch) {
    if (/*kiểm tra điều kiện đặt bit ch ở (i, j)*/) {
      f[i][j][mask] += cal(i + (j == N), j % N + 1, (mask &lt;&lt; 1) &amp; ((1 &lt;&lt; N) - 1) + ch);
    }
  }
  return f[i][j][mask];
}

// x &amp; ((1 &lt;&lt; N) - 1) để lấy x % (1 &lt;&lt; N), N bit cuối của x.
// (x &lt;&lt; 1) == x * 2, đẩy các bit sang phải 1 đơn vị.

int ans = cal(1, 1, 0);
</code></pre>

<h2 id="domino-bài-toán-thứ-hai-1">DOMINO (Bài toán thứ hai)</h2>

<p>Lần này không có ô cấm. Điều kiện như bài toán thứ nhất.</p>

<h4 id="giới-hạn-8">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 8</code>, <code>1 &lt;= M &lt;= 10^6</code></p>

<h3 id="nhân-ma-trận">Nhân ma trận</h3>

<p>Lần này không có ô cấm, nên với mỗi hàng ta chỉ cần quan tâm mask của nó là gì. Từ đây ta có thể nghĩ đến việc nhân ma trận.</p>

<p>Giới hạn <code>N</code> nhỏ, <code>M</code> lớn cũng mang đến cho ta gợi ý này.</p>

<h3 id="số-trạng-thái">Số trạng thái</h3>

<p><code>O((2 ^ N)^3 * log(M))</code> chưa thể thỏa mãn bài toán. Ta phân tích thêm một chút: với mỗi cột, ta có thể loại ra các trạng thái không thỏa mãn các điều kiện giữa 2 ô liên tiếp trên cùng cột.</p>

<p>Việc thử nghiệm cho thấy với <code>N = 8</code> cũng chỉ có <strong>55</strong> trạng thái, có thể nhân ma trận.</p>

<h3 id="điều-kiện-theo-i-j">Điều kiện theo <code>i + j</code></h3>

<p>Khi chuyển từ cột <code>2i</code> sang cột <code>2i + 1</code>, điều kiện bị thay đổi: thứ tự các ô trong cột đang từ <em>lẻ, chẵn, lẻ, ...</em> thành <em>chẵn, lẻ, chẵn,...</em> Điều này làm cho việc chuyển trạng thái không thể thực hiện đơn thuần.</p>

<p>Ta có thể sửa điều này bằng cách thêm 1 bit cho trạng thái của cột, chỉ xem đây là trạng thái cho cột lẻ hay chẵn.</p>

<p>Bảng chuyển đổi của mình sẽ chỉ cho phép chuyển từ cột lẻ sang chẵn và ngược lại.</p>

<p>Để đơn giản từ giờ ta gọi số trạng thái là <code>P</code> (<code>P &lt;= 110</code>).</p>

<h3 id="ma-trận-gốc-và-đáp-số">Ma trận gốc và đáp số</h3>

<p>Hiển nhiên ta trận gốc là một ma trận <code>1 x P</code>, trong đó các ô thể hiện trạng thái cột lẻ sẽ là <code>1</code>. Ta nhân ma trận gốc với bảng chuyển đổi đã lũy thừa <code>M - 1</code>, nhận được ma trận đáp số <code>1 x P</code>. Đáp án chính là tổng các phần tử trong ma trận.</p>

<p>Độ phức tạp là <code>O(P ^ 3 * log(M))</code>.</p>

<h2 id="games-1">GAMES</h2>

<h3 id="tóm-tắt-đề-bài-3">Tóm tắt đề bài</h3>

<p>Cho một dãy bit <code>N</code> phần tử chưa xác định và <code>M</code> điều kiện có dạng xor của <code>A[l..r]</code> là 0 hay 1. Tìm vị trí <code>x</code> đầu tiên mà điều kiện <code>x</code> mâu thuẫn với các điều kiện trên.</p>

<h4 id="giới-hạn-9">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 10^9</code>, <code>1 &lt;= M &lt;= 10^5</code></p>

<h3 id="chặt-nhị-phân">Chặt nhị phân</h3>

<p>Để tìm vị trí đầu tiên mâu thuẫn, ta chặt nhị phân <code>x</code> để tìm vị trí xa nhất mà vẫn tồn tại một dãy thỏa mãn các điều kiện từ 1 đến <code>x</code>.</p>

<p>Bài toán trở thành kiểm tra xem có một dãy tồn tại không.</p>

<h3 id="tính-chất-mảng-dồn">Tính chất mảng dồn</h3>

<p>Nếu ta xét mảng dồn <code>S[1..N]</code>, thì điều kiện tổng xor <code>l..r</code> bằng 0 hay 1 tương đương với <code>S[l - 1]</code> với <code>S[r]</code> bằng nhau hay khác nhau.</p>

<p>Ngoài ra, <code>S[i]</code> có thể nhận được bất kí giá trị nào không phụ thuộc vào <code>S[i - 1]</code> nên ta có thể thoải mái gán một giá trị bất kì, nhưng chỉ <strong>một</strong> mà thôi.</p>

<p>Bài toán trở thành, liệu có thể gán dãy <code>S[1..N]</code> thỏa mãn các điều kiện các nhau không?</p>

<h3 id="2-giá-trị-cho-1-biến">2 giá trị cho 1 biến</h3>

<p>Ta có thể coi mảng <code>S[]</code> như một đồ thị <code>N</code> đỉnh. Gộp các đỉnh cùng giá trị, ta thấy việc gán giá trị 0-1 cho các đỉnh còn lại giống như tô màu 2 phía.</p>

<p>Như vậy, ta có thể kiểm tra xem đồ thị có phải 2 phía không.</p>

<p>Độ phức tạp sẽ là <code>O(M + N)</code>.</p>

<h3 id="giảm-số-lượng-đỉnh">Giảm số lượng đỉnh</h3>

<p>Có tận <code>10^9</code> đỉnh, tuy nhiên chỉ có <code>10^5</code> cạnh. Vì thế chỉ có tối đa <code>2 * 10^5</code> đỉnh có bậc khác 0, ta chỉ cần quan tâm tới các đỉnh này.</p>

<p>Độ phức tạp chỉ còn <code>O(M)</code>, mang lại thuật toán <code>O(M * log(M))</code>.</p>

<h2 id="hanoi-1">HANOI</h2>

<h3 id="tóm-tắt-đề-bài-4">Tóm tắt đề bài</h3>

<p>Cho thuật toán giải bài toán tháp Hà Nội:</p>

<pre><code class="language-python">def HanoiTower(height, From, Temp, To):
  if height == 1:
    # Move one from `From` to `To`
    return
  HanoiTower(height - 1, From, To, Temp)
  HanoiTower(1, From, Temp, To)
  HanoiTower(height - 1, Temp, From, To)

# Call the function
HanoiTower(N, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;)
</code></pre>
<ul><li>Tìm trạng thái của 3 tháp sau <code>P</code> lần gọi hàm.</li>
<li>Cho trạng thái của 3 tháp, tính số bước đã gọi hàm <code>P</code>, hoặc in <code>-1</code> nếu không tồn tại trạng thái đó khi giải.</li></ul>

<h4 id="giới-hạn-10">Giới hạn</h4>

<p><code>1 &lt;= N &lt;= 100</code>. Lưu ý <code>P</code> có thể là số lớn.</p>

<h3 id="các-bước-của-thuật-toán">Các bước của thuật toán</h3>

<p>Ta có thể tóm tắt thuật toán trong 3 bước:
 – Chuyển tháp <code>N - 1</code> từ A sang B dùng C làm đệm
 – Chuyển đĩa <code>N</code> từ A sang C
 – Chuyển tháp <code>N - 1</code> từ B sang C dùng A làm đệm</p>

<p>Từ thuật toán, ta có thể xác định mình đang ở bước nào bằng cách xét vị trí của đĩa <code>N</code>.
– Nếu <code>N</code> còn ở <code>A</code> thì ta ở bước 1.
– Nếu không ta ở bước 2 hoặc 3.</p>

<p>Sau khi xác định được vị trí của <code>N</code>, ta có thể bỏ nó đi và đệ quy xuống bước dưới, coi như ta đang giải bài toán chuyển tháp <code>N - 1</code>.</p>

<h3 id="tìm-trạng-thái-từ-p">Tìm trạng thái từ <code>P</code></h3>

<p>Ta biết để chuyển tháp <code>x</code> sẽ mất <code>2^x - 1</code> bước, nên khi xét vị trí đĩa <code>N</code> ta có thể xác định xem ta đang ở bước mấy của việc chuyển tháp <code>N</code>:
– Nếu <code>P &lt; 2^x</code> thì ta đang ở bước 1.
– Nếu <code>P = 2^x</code> thì ta đang ở bước 2.
– Nếu <code>P &gt; 2^x</code> thì ta đang ở lượt <code>P - 2^x</code> của bước 3.</p>

<p>Tùy theo bước ta xác định vị trí của đĩa <code>N</code> rồi đệ quy xuống bước tương ứng. Độ phức tạp là <code>O(N)</code>. Code khá giống bò trên BST.</p>

<h3 id="tìm-p-từ-trạng-thái">Tìm <code>P</code> từ trạng thái</h3>

<p>Việc tìm <code>P</code> không khác gì tìm trạng thái. Khi xét tháp <code>N</code>, ta kiểm tra xem mình ở bước nào tùy theo vị trí của đĩa <code>N</code>:
– Nếu <code>N</code> ở A, thì ta ở bước 1. Đệ quy vào bước 1.
– Nếu <code>N</code> ở C, ta ở bước 2 hoặc 3. Cộng <code>P</code> thêm <code>2^x</code> (cho bước 1+2) rồi đệ quy vào 3.</p>

<p>Độ phức tạp cũng là <code>O(N)</code>.</p>

<h2 id="wg-1">WG</h2>

<h3 id="tóm-tắt-đề-bài-5">Tóm tắt đề bài</h3>

<p>Cho một xâu <code>P</code> và một tập xâu <code>S[1..N]</code>. Dựng xâu <code>T</code> như sau:
– Đầu tiên chọn một xâu trong tập <code>S[]</code> và thêm vào <code>T</code>
– Sau đó, tìm xâu <code>S[i]</code> bất kì thỏa mãn <code>S[0] == T.back()</code> rồi thêm <code>S[i][1..]</code> vào.</p>

<p>Hỏi xâu <code>T</code> ngắn nhất chứa dãy con không liên tiếp <code>P</code> là gì?</p>

<h4 id="giới-hạn-11">Giới hạn</h4>

<p><code>1 &lt;= |P| &lt;= 250, 1 &lt;= |S[i]| &lt;= 10, 1 &lt;= N &lt;= 1000</code></p>

<h3 id="tham-lam-dựng-xâu">Tham lam dựng xâu</h3>

<p>Khi đã có xâu <code>T</code> có thể lấy ra được dãy con là prefix <code>x</code> của <code>P</code>, ta có thể xác định số lượng kí tự ghép thêm khi thêm xâu <code>S[i]</code> bằng cách đi từ trái sang phải, tham lam kí tự tiếp theo cần ghép.</p>

<p>Từ đó ta tính trước được mảng <code>nx[i][j]</code>, khi thêm xâu <code>i</code> với <code>j</code> kí tự đã ghép thì trạng thái mới là bao nhiêu. Độ phức tạp sẽ là <code>O(N * |P| * |S[i]|)</code>.</p>

<h3 id="quy-hoạch-động-1">Quy hoạch động</h3>

<p>Ta có thể quy hoạch động <code>f[i][j]</code> là độ dài xâu <code>T</code> ngắn nhất sao cho xâu cuối cùng là <code>i</code> và đã ghép được <code>j</code> kí tự đầu tiên của <code>P</code>. Ta chọn thêm một xâu <code>S[k]</code> mới và chuyển trạng thái sang <code>f[k][nx[k][j]]</code>. Điều kiện là <code>S[i].back() == S[k][0]</code> và <code>nx[k][j] != j</code>.</p>

<p>Độ phức tạp sẽ là <code>O(N^2 * |P|)</code>, chưa thỏa mãn bài toán.</p>

<h3 id="kí-tự-cuối">Kí tự cuối</h3>

<p>Thực chất ta không cần lưu chiều <code>i</code> là xâu cuối cùng, vì ta chỉ cần quan tâm đến kí tự cuối cùng của <code>T</code>, nên thay vào đó ta có thể chỉ lưu <code>i</code> là kí tự cuối cùng.</p>

<p>Độ phức tạp giảm xuống còn <code>O(26 * |P| * N)</code>, thỏa mãn bài toán.</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/2017-04-20-training</guid>
      <pubDate>Fri, 21 Apr 2017 16:36:25 +0000</pubDate>
    </item>
    <item>
      <title>2017-04-19 Training</title>
      <link>https://blog.dtth.ch/nki/2017-04-19-training</link>
      <description>&lt;![CDATA[#training #apio #vietnamese #thầyPhương&#xA;&#xA;Thầy Phương cho 3 bài của FARIO 2017. Bài 1 đã làm rồi, bài 3 là bài approximate nên mình chỉ chữa bài 2.&#xA;&#xA;!--more--&#xA;&#xA;Pyramid Cake&#xA;Tóm tắt đề bài&#xA;Cho một chiếc hộp có đáy chữ nhật M x N, vị trí (i, j) có độ cao là Hi. Ta dựng một chiếc bánh nhiều tần thỏa mãn:&#xA; Các tầng là các hình chữ nhật chứa đỉnh (1,1)&#xA; Tầng trên phải nằm bên trong mặt phẳng của tầng dưới&#xA; Không ô nào cao hơn vị trí tương ứng của hộp&#xA; Thể tích bánh là lớn nhất.&#xA;Giới hạn&#xA;1 &lt;= N, M &lt;= 1000, 1 &lt;= Hi &lt;= 10^8&#xA;&#xA;Lựa chọn tầng&#xA;Do mọi tầng đề chứa (1, 1), bản chất ta chỉ cần tọa độ của góc còn lại là có thể xác định được duy nhất tầng hiện tại.&#xA;&#xA;Tại sao ta chỉ cần quan tâm số tầng mà không phải độ cao? Hiển nhiên, với tầng (i, j) ta biết độ cao của tầng đó (nếu tính cả các tầng dưới nó) sẽ là Mi = min(Hi&#39;) với i&#39; &lt;= i, j&#39; &lt;= j.&#xA;&#xA;Khi nén tầng như vậy, ta có thể coi như tầng sau luôn nhỏ hơn tầng dưới, làm cho tập trạng thái không có chu trình và ta có thể quy hoạch động được.&#xA;&#xA;Gọi fi là diện tích lớn nhất của hình có tầng dưới cùng là (i, j). Hiển nhiên ta sẽ đặt tầng dưới cùng độ dày Mi. Sau đó, ta chọn một tầng nhỏ hơn để qhđ:&#xA;&#xA;int cur = Mi  i  j; // Diện tích phần đáy&#xA;for (int k = 1; k &lt;= i; ++k)&#xA;&#x9;for (int l = 1; l &lt;= j; ++l)&#xA;&#x9;&#x9;if (k != i || l != j)&#xA;&#x9;&#x9;&#x9;fi = max(fi, cur + fk - Mi  k  l);&#xA;&#xA;Tại sao ta trừ đi Mi  k  l? Bởi phần diện tích này đã được tính vào đáy của hình dưới cùng.&#xA;&#xA;Đáp số sẽ là max(fi), độ phức tạp là O(N^4), chưa đủ để giải quyết bài toán.&#xA;&#xA;Thử tất cả?&#xA;&#xA;Ta có thể thấy, mỗi hình chữ nhật con đều nhỏ hơn đáy ban đầu ít nhất 1 hàng hoặc 1 cột. Vậy tại sao mình không chọn 1 trong 2 hình to nhất ((i, j - 1) và (i - 1, j)) để làm đáy tiếp theo?&#xA;&#xA;Rất có thể mọi người sẽ nghĩ việc này không đúng vì có thể không điền đc thêm tầng nào - nhưng nếu ta nghĩ theo cách nhìn khác - ta thêm 0 tầng, thì lựa chọn vẫn hợp lí.&#xA;&#xA;Có thể việc này không tối ưu không? Giả sử, lựa chọn (x, y) (x &lt; i, y &lt; j) là tối ưu. Hiển nhiên nó cũng sẽ tối ưu cho (i, j - 1). Vậy ta hoàn toàn có thể chọn (i, j - 1) và nó sẽ chứa cả (x, y), kết quả không đổi.&#xA;&#xA;Cải thiện thuật toán&#xA;Ta rút việc chọn tất cả cặp thành chọn một trong hai hình chữ nhật con lớn nhất. Độ phức tạp giảm xuống còn O(N^2), thỏa mãn bài toán.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/nki/tag:training" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">training</span></a> <a href="/nki/tag:apio" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">apio</span></a> <a href="/nki/tag:vietnamese" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">vietnamese</span></a> <a href="/nki/tag:th%E1%BA%A7yPh%C6%B0%C6%A1ng" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">thầyPhương</span></a></p>

<p>Thầy Phương cho 3 bài của <a href="http://orac.amt.edu.au/cgi-bin/train/hub.pl?expand=fario17#fario17" rel="nofollow">FARIO 2017</a>. Bài 1 đã làm rồi, bài 3 là bài approximate nên mình chỉ chữa bài 2.</p>



<h2 id="pyramid-cake">Pyramid Cake</h2>

<h3 id="tóm-tắt-đề-bài">Tóm tắt đề bài</h3>

<p>Cho một chiếc hộp có đáy chữ nhật <code>M x N</code>, vị trí <code>(i, j)</code> có độ cao là <code>H[i][j]</code>. Ta dựng một chiếc bánh nhiều tần thỏa mãn:
 – Các tầng là các hình chữ nhật chứa đỉnh <code>(1,1)</code>
 – Tầng trên phải nằm bên trong mặt phẳng của tầng dưới
 – Không ô nào cao hơn vị trí tương ứng của hộp
 – Thể tích bánh là lớn nhất.</p>

<h4 id="giới-hạn">Giới hạn</h4>

<p><code>1 &lt;= N, M &lt;= 1000</code>, <code>1 &lt;= H[i][j] &lt;= 10^8</code></p>

<h3 id="lựa-chọn-tầng">Lựa chọn tầng</h3>

<p>Do mọi tầng đề chứa <code>(1, 1)</code>, bản chất ta chỉ cần tọa độ của góc còn lại là có thể xác định được duy nhất tầng hiện tại.</p>

<p>Tại sao ta chỉ cần quan tâm số tầng mà không phải độ cao? Hiển nhiên, với tầng <code>(i, j)</code> ta biết độ cao của tầng đó (nếu tính cả các tầng dưới nó) sẽ là <code>M[i][j] = min(H[i&#39;][j&#39;])</code> với <code>i&#39; &lt;= i, j&#39; &lt;= j</code>.</p>

<p>Khi nén tầng như vậy, ta có thể coi như tầng sau luôn nhỏ hơn tầng dưới, làm cho tập trạng thái không có chu trình và ta có thể quy hoạch động được.</p>

<p>Gọi <code>f[i][j]</code> là diện tích lớn nhất của hình có tầng dưới cùng là <code>(i, j)</code>. Hiển nhiên ta sẽ đặt tầng dưới cùng độ dày <code>M[i][j]</code>. Sau đó, ta chọn một tầng nhỏ hơn để qhđ:</p>

<pre><code class="language-cpp">int cur = M[i][j] * i * j; // Diện tích phần đáy
for (int k = 1; k &lt;= i; ++k)
	for (int l = 1; l &lt;= j; ++l)
		if (k != i || l != j)
			f[i][j] = max(f[i][j], cur + f[k][l] - M[i][j] * k * l);
</code></pre>

<p>Tại sao ta trừ đi <code>M[i][j] * k * l</code>? Bởi phần diện tích này đã được tính vào đáy của hình dưới cùng.</p>

<p>Đáp số sẽ là <code>max(f[i][j])</code>, độ phức tạp là <code>O(N^4)</code>, chưa đủ để giải quyết bài toán.</p>

<h3 id="thử-tất-cả">Thử tất cả?</h3>

<p>Ta có thể thấy, mỗi hình chữ nhật con đều nhỏ hơn đáy ban đầu ít nhất 1 hàng hoặc 1 cột. Vậy tại sao mình không chọn 1 trong 2 hình to nhất (<code>(i, j - 1)</code> và <code>(i - 1, j)</code>) để làm đáy tiếp theo?</p>

<p>Rất có thể mọi người sẽ nghĩ việc này không đúng vì có thể không điền đc thêm tầng nào – nhưng nếu ta nghĩ theo cách nhìn khác – ta thêm <strong>0</strong> tầng, thì lựa chọn vẫn hợp lí.</p>

<p>Có thể việc này không tối ưu không? Giả sử, lựa chọn <code>(x, y)</code> (<code>x &lt; i, y &lt; j</code>) là tối ưu. Hiển nhiên nó cũng sẽ tối ưu cho <code>(i, j - 1)</code>. Vậy ta hoàn toàn có thể chọn <code>(i, j - 1)</code> và nó sẽ chứa cả <code>(x, y)</code>, kết quả không đổi.</p>

<h3 id="cải-thiện-thuật-toán">Cải thiện thuật toán</h3>

<p>Ta rút việc chọn tất cả cặp thành chọn một trong hai hình chữ nhật con lớn nhất. Độ phức tạp giảm xuống còn <code>O(N^2)</code>, thỏa mãn bài toán.</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/2017-04-19-training</guid>
      <pubDate>Wed, 19 Apr 2017 13:00:00 +0000</pubDate>
    </item>
    <item>
      <title>2017/04/15 Training</title>
      <link>https://blog.dtth.ch/nki/2017-04-15-training</link>
      <description>&lt;![CDATA[#training #apio #english #anhMinh&#xA;&#xA;Warning: If you want to try solving the problems, skip reading everything but the statements! Also, for hints, read slowly from top to bottom of each problem.&#xA;&#xA;I didn&#39;t attend the class directly, but did the problems while going on a bus to Ninh Binh. The problems were somewhat exciting to me.&#xA;&#xA;!--more--&#xA;&#xA;A. Sorting&#xA;Statements&#xA;You are given a permutation \\(A[1\dots n]\\), along with \\(Q\\) queries, each of the form \\(l, r\\) which you should sort the subarray \\(A[l..r]\\) increasingly or decreasingly. After all the queries are processed, print the middle value \\(A[N / 2 + 1]\\).&#xA;Contraints&#xA;\\(1 \le N \le 10^5\\), \\(1 \le Q \le 10^5\\), \\(N\\) is odd.&#xA;One problem from the past&#xA;The statements reminds me of a past problem I did on Codeforces. I didn&#39;t remember the source, but in short you are given a Latin string with the same sorting queries, and then you have to return the whole string. It was feasible to solve the problem in \\(O(Q \times 26 \times log(N))\\) time because of the limited alphabet size.&#xA;&#xA;The main idea is, for each \\(l, r\\) query, count the number of instances of each character in the range. Then we can re-assign the characters&#39; positions, from the smallest to the largest. To efficiently do range-counting and range-assignments we can maintain 26 interval trees, with lazy update.&#xA;&#xA;However, the problem I faced yesterday was different: the alphabet size is much bigger. Perhaps a different approach was needed. Or maybe not?&#xA;The alphabet size&#xA;From the previous problem, we know that the problem can be solved efficiently if the alphabet size was small. &#34;Is there anyway to make the numbers pool smaller?&#34; - that was the first question that came to my mind.&#xA;&#xA;The above problem somewhat resembles radix sorting, so of course it can also be applied to numbers. However splitting digits is not eligible, as reordering still takes too much time. We need to transform numbers into something that&#39;s both small in size and easy to reassign, maybe not even caring about its original value.&#xA;&#xA;The problem only asked for one element. What if, all we care about is the element itself?&#xA;Relative ordering&#xA;It turns out that we don&#39;t actually need to sort the elements. Not entirely.&#xA;&#xA;Let&#39;s choose a pivot, \\(X\\). We transform every number larger than \\(X\\) to \\(1\\), and the rest to \\(0\\). Sorting becomes wrong now, but the order relative to \\(X\\) isn&#39;t: If we sort \\(l, r\\) increasingly, every number that&#39;s smaller than \\(X\\) still stays on the left of those which are larger than \\(X\\). Therefore, this masked ordering isn&#39;t entirely wrong, because the correct ordering have the same mask, and we can assume that the unlerlying original values are on the correct positions. Of course, we don&#39;t need to care about it.&#xA;&#xA;Of course, since the numbers are binary now, performing the above algorithm becomes a breeze.&#xA;&#xA;After all the sortings, we get the middle number&#39;s mask. It doesn&#39;t give us the answer immediately, but it does leave a hint: If the number is 1 then the answer is larger than \\(X\\), and vice versa.&#xA;Binary Search&#xA;To effectively use the previous hint, we can perform binary search on the value of \\(A[N / 2 + 1]\\), checking whether it&#39;s larger than the middle value, and shorten the range according to the answer.&#xA;That is also the final missing piece to solve the problem, giving us an \\(O(log(N) \times Q \times log(N))\\) algorithm.&#xA;&#xA;B. Zigzag&#xA;Statements&#xA;You are given an array \\(A[1..N]\\) of distinct numbers. There are also \\(Q\\) queries, each of the form \\(x y\\) that asks you to change the \\(x\\)-th number to \\(y\\). It is guaranteed that after each query the array always contains dintinct numbers. After each query you have to return the largest alternating subsequence of the array.&#xA;Constraints&#xA;\\(1 \le N \le 10^6\\), \\(1 \le A[i] \le 10^9\\)&#xA;Alternating Subsequence&#xA;The first thing to do is analyzing the longest alternating subsequence problem. It turns out that it is not as difficult as it sounds, in fact it can be greedily built from an array with distinct numbers.&#xA;&#xA;One nice observation from the problem: If there exists such \\(i\\) that \\(A[i - 1] \le A[i] \le A[i + 1]\\), then there always exists an optimal subsequence without \\(A[i]\\). Why? Since they&#39;re consecutive numbers, \\(A[i - 1]\\) is always better than \\(A[i]\\) as a &#34;lower&#34; number, and \\(A[i + 1]\\) is always better than \\(A[i]\\) as an &#34;upper&#34; numbber. Therefore, it is safe to just remove \\(A[i]\\) from the array without losing the optimal sequence.&#xA;&#xA;Now let&#39;s continuously remove such numbers from the array until there is no such one. Which means, for each \\(i\\), it is either \\(A[i - 1] \le A[i] \land A[i] \ge A[i + 1]\\) or \\(A[i - 1] \ge A[i] \land A[i] \le A[i + 1]\\). Wait... Isn&#39;t \\(A[..]\\) now already an alternating sequence? More than that, \\(A[..]\\) is an optimal longest alternating subsequence of the original array.&#xA;&#xA;Dive a little deeper, we will find out that each \\(A[i]\\) can be a part of the new array if and only if has the same above atrribute on the original array. Unless it&#39;s the first or last number of the array, in such case it&#39;s always included.&#xA;&#xA;Testing an element&#xA;With the above observations, we can deduce whether an element will appear in our optimal subsequence:&#xA; It&#39;s the first or last element, or&#xA; Either it&#39;s both smaller or both bigger than its neighboring elements.&#xA;&#xA;Using these conditions we can check each element in \\(O(1)\\). To answer the length of the optimal subsequence one only has to count how many elements satisfies the above conditions.&#xA;Processing queries&#xA;If we change one number, how is each element&#39;s satisfiability affected? It turns out, only 3 of them are affected at most: the element itself, and its neighboring elements.&#xA;&#xA;It is now easy to process each query in \\(O(1)\\): just change the element and re-check every affected elements.&#xA;&#xA;That concludes our \\(O(N + M)\\) algorithm.&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><a href="/nki/tag:training" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">training</span></a> <a href="/nki/tag:apio" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">apio</span></a> <a href="/nki/tag:english" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">english</span></a> <a href="/nki/tag:anhMinh" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">anhMinh</span></a></p>

<p><strong>Warning</strong>: If you want to try solving the problems, skip reading everything but the statements! Also, for hints, read <em>slowly</em> from top to bottom of each problem.</p>

<p>I didn&#39;t attend the class directly, but did the problems while going on a bus to Ninh Binh. The problems were somewhat exciting to me.</p>



<h2 id="a-sorting">A. Sorting</h2>

<h3 id="statements">Statements</h3>

<p>You are given a permutation \(A[1\dots n]\), along with \(Q\) queries, each of the form \(l, r\) which you should sort the subarray \(A[l..r]\) increasingly or decreasingly. After all the queries are processed, print the middle value \(A[N / 2 + 1]\).</p>

<h4 id="contraints">Contraints</h4>

<p>\(1 \le N \le 10^5\), \(1 \le Q \le 10^5\), \(N\) is odd.</p>

<h3 id="one-problem-from-the-past">One problem from the past</h3>

<p>The statements reminds me of a past problem I did on Codeforces. I didn&#39;t remember the source, but in short you are given a Latin string with the same sorting queries, and then you have to return the whole string. It was feasible to solve the problem in \(O(Q \times 26 \times log(N))\) time because of the limited alphabet size.</p>

<p>The main idea is, for each \(l, r\) query, count the number of instances of each character in the range. Then we can re-assign the characters&#39; positions, from the smallest to the largest. To efficiently do range-counting and range-assignments we can maintain 26 <em>interval trees</em>, with lazy update.</p>

<p>However, the problem I faced yesterday was different: the alphabet size is much bigger. Perhaps a different approach was needed. Or maybe not?</p>

<h3 id="the-alphabet-size">The alphabet size</h3>

<p>From the previous problem, we know that the problem can be solved efficiently if the alphabet size was small. “Is there anyway to make the numbers pool smaller?” – that was the first question that came to my mind.</p>

<p>The above problem somewhat resembles radix sorting, so of course it can also be applied to numbers. However splitting digits is not eligible, as reordering still takes too much time. We need to transform numbers into something that&#39;s both small in size and easy to reassign, maybe not even caring about its original value.</p>

<p>The problem only asked for <strong>one</strong> element. What if, all we care about is the element itself?</p>

<h3 id="relative-ordering">Relative ordering</h3>

<p>It turns out that we don&#39;t actually need to sort the elements. Not entirely.</p>

<p>Let&#39;s choose a pivot, \(X\). We transform every number larger than \(X\) to \(1\), and the rest to \(0\). Sorting becomes wrong now, but the order <em>relative to \(X\)</em> isn&#39;t: If we sort \(l, r\) increasingly, every number that&#39;s smaller than \(X\) still stays on the left of those which are larger than \(X\). Therefore, this <em>masked</em> ordering isn&#39;t entirely wrong, because the correct ordering <strong>have the same mask</strong>, and we can assume that the unlerlying original values are on the correct positions. Of course, we don&#39;t need to care about it.</p>

<p>Of course, since the numbers are binary now, performing the above algorithm becomes a breeze.</p>

<p>After all the sortings, we get the middle number&#39;s mask. It doesn&#39;t give us the answer immediately, but it does leave a hint: If the number is <strong>1</strong> then the answer is <strong>larger than \(X\)</strong>, and vice versa.</p>

<h3 id="binary-search">Binary Search</h3>

<p>To effectively use the previous hint, we can perform <em>binary search</em> on the value of \(A[N / 2 + 1]\), checking whether it&#39;s larger than the middle value, and shorten the range according to the answer.
That is also the final missing piece to solve the problem, giving us an \(O(log(N) \times Q \times log(N))\) algorithm.</p>

<h2 id="b-zigzag">B. Zigzag</h2>

<h3 id="statements-1">Statements</h3>

<p>You are given an array \(A[1..N]\) of distinct numbers. There are also \(Q\) queries, each of the form \(x y\) that asks you to change the \(x\)-th number to \(y\). It is guaranteed that after each query the array always contains dintinct numbers. After each query you have to return the largest alternating subsequence of the array.</p>

<h4 id="constraints">Constraints</h4>

<p>\(1 \le N \le 10^6\), \(1 \le A[i] \le 10^9\)</p>

<h3 id="alternating-subsequence">Alternating Subsequence</h3>

<p>The first thing to do is analyzing the <strong>longest alternating subsequence</strong> problem. It turns out that it is not as difficult as it sounds, in fact it can be greedily built from an array with distinct numbers.</p>

<p>One nice observation from the problem: If there exists such \(i\) that \(A[i – 1] \le A[i] \le A[i + 1]\), then there always exists an optimal subsequence without \(A[i]\). Why? Since they&#39;re consecutive numbers, \(A[i – 1]\) is always better than \(A[i]\) as a “lower” number, and \(A[i + 1]\) is always better than \(A[i]\) as an “upper” numbber. Therefore, it is safe to just remove \(A[i]\) from the array without losing the optimal sequence.</p>

<p>Now let&#39;s continuously remove such numbers from the array until there is no such one. Which means, for each \(i\), it is either \(A[i – 1] \le A[i] \land A[i] \ge A[i + 1]\) or \(A[i – 1] \ge A[i] \land A[i] \le A[i + 1]\). Wait... Isn&#39;t \(A[..]\) now already an alternating sequence? More than that, \(A[..]\) is an <strong>optimal</strong> longest alternating subsequence of the original array.</p>

<p>Dive a little deeper, we will find out that each \(A[i]\) can be a part of the new array <strong>if and only if</strong> has the same above atrribute on the original array. <em>Unless</em> it&#39;s the first or last number of the array, in such case it&#39;s always included.</p>

<h3 id="testing-an-element">Testing an element</h3>

<p>With the above observations, we can deduce whether an element will appear in our optimal subsequence:
 – It&#39;s the first or last element, <em>or</em>
 – Either it&#39;s both smaller or both bigger than its neighboring elements.</p>

<p>Using these conditions we can check each element in \(O(1)\). To answer the length of the optimal subsequence one only has to count how many elements satisfies the above conditions.</p>

<h3 id="processing-queries">Processing queries</h3>

<p>If we change one number, how is each element&#39;s satisfiability affected? It turns out, only 3 of them are affected at most: the element itself, and its neighboring elements.</p>

<p>It is now easy to process each query in \(O(1)\): just change the element and re-check every affected elements.</p>

<p>That concludes our \(O(N + M)\) algorithm.</p>
]]></content:encoded>
      <guid>https://blog.dtth.ch/nki/2017-04-15-training</guid>
      <pubDate>Sun, 16 Apr 2017 10:34:00 +0000</pubDate>
    </item>
  </channel>
</rss>