以下是我考場上的思路,很多題都不是正解。對於某些題目,我們使用《代碼部落》中的題解,希望大家能夠看懂
JOISC2020 Round1 自閉記
T1
11 pts 算法:考慮\(DP\)。
設\(f_{i, j, k}\)表示前\(i\)個數,選了\(j\)個\(B\)數組的數,且第\(i\)個數選的是\(A/B\)時,是否存在所要求的單調不降的數列。
直接\(DP\),可以獲得一個\(O(n^2)\)的做法。
100 pts 算法:
我們通過猜結論/打表發現一個規律。如果固定了\(i, k\)的話,那么使得\(f_{i, j, k} = 1\)的\(j\)的集合是一段連續的區間。
因此,我們可以縮減狀態。
記錄\(i, k\)固定的時候使得\(f_{i, j, k} = 1\)的\(j\)的最小和最大值。
這樣轉移變成取\(max/min\)。
考慮如何還原方案。
倒着填,通過\(DP\)結果判斷是否能填A或者B。能填\(A\)則填\(A\),否則填\(B\)。
時間復雜度\(O(n)\),可以獲得\(100\)分。
大概證明:對\(i\)歸納證明固定\(i, k \in \{0, 1 \}\)時,\(j\)的取值是一段區間,且固定\(i\),\(k\)為\(0,1\)之間的任意值時\(j\)的取值是一段區間。
T2
本人考場上只拿了\(3\)分。
\(2\)分做法:
\(k = 1\)時,這個點在\(n\)個長方形的交集內。
每個長方形的橫坐標取值為\([L_i, R_i]\),縱坐標取值為\([D_i, U_i]\)。
我們只需要求\(L\)的最大值,\(R\)的最小值,\(D\)的最大值,\(U\)的最小值。
然后在這上面任意放一個點即可。
\(3\)分做法:
\(k = 2, n \leq 2000\)時。
我們考慮求出\(R\)的最小值\(r\)。我們可以發現可以通過調整使得存在一個點的橫坐標為\(r\)。枚舉它的縱坐標(在這\(2n\)個D數組和U數組的值中枚舉),然后把它能覆蓋的長方形刪掉。再對剩下的長方形套用\(k = 1\)的算法。
時間復雜度\(O(n^2)\),可以通過\(3\)分。
\(6\)分做法:
\(k = 2, n \leq 200000\)時,我們考慮從小到大對\(r\)做掃描線,在掃描線的過程中動態維護剩下的長方形中\(L, D\)的最大值和\(R, U\)的最小值。
實現時可以用\(4\)個堆來維護。
時間復雜度\(O(n \log n)\),可以通過\(6\)分。
\(k = 3\)的做法:
實際上\(k \leq 3\)的時候可以\(O(n)\)。
我們求出\(L\)的最大值\(Lmax\),\(R\)的最小值\(Rmin\),\(D\)的最大值\(Dmax\),\(U\)的最小值\(Umin\)。
如果\(Lmax \leq Rmin\),轉換為\(1\)維問題,用區間覆蓋算法。
否則形成的矩形的\(4\)條頂點中總有一個點要被選。
枚舉這個點被不被選,遞歸下去求解即可。
時間復雜度\(O(n)\)。
\(k = 4, n \leq 2000\)的做法:
這時候我們需要考慮\(4\)條邊中每條邊恰好選\(1\)個點的情況。
枚舉一條邊上的一個點(通過離散化,它可以只有\(O(n)\)種取值),再套用\(k \leq 3\)的算法,可以獲得一個\(O(n^2)\)的算法。
通過這些分析,我們可以獲得\(21\)分。
正確性沒有保證的做法:
隨機\(k\)個矩形,將剩余的矩形隨機排列。我們按照這個排列的順序把每個矩形分為\(k\)類中的一類。假設排在它前面的矩形已經分划完畢,我們算出如果把這個矩形加入第\(i\)個集合,那么計算出這個集合的新的矩形交的面積大小/原來的面積大小。我們找其中最大者,加入那個集合。
我們多隨機幾次,直到划分出來的\(k\)組矩形都交集非空為止。
正確性有保證的做法:
考慮我們之前還剩下一個矩形且這個矩形的\(4\)條邊上各有一個點的情況。使用\(2-SAT\)解決這個問題。
設這個矩形的橫坐標范圍為\([L,R]\),縱坐標范圍為\([D,U]\)
比如說一個點的橫坐標為\(L\),且縱坐標為\([D,U]\)中的某個數\(y\)。容易發現可以離散化使得縱坐標只有\(O(n)\)種取值。
我們把矩形縱坐標邊界的值拿來離散化,作為關鍵點\(y_1 \leq ... \leq y_k\)。對這條邊上的一個點,設立\(k\)個\(01\)變量,分別表示\(y \leq y_i\)。
然后我們發現\(y \leq y_i \rightarrow y \leq y_{i + 1}\)。於是我們可以用\(2-SAT\)刻畫這條邊上恰好選一個點的條件。
接下來我們要刻畫每個矩形中至少有一個點的條件。
如果是兩條邊的至少一個區間上有一個點,相當於要求\(y_1 \in [l_1, r_1] \or y_2 \in [l_2, r_2]\)。
把這個條件轉換成如下\(4\)組推導關系。
\(y_1 < l_1 \rightarrow y_2 \ge l_2, y_1 < r_1 \rightarrow y_2 \leq r_2\)
\(y_1 > r_1 \rightarrow y_2 \ge l_2\)
\(y_1 > r_1 \rightarrow y_2 \leq r_2\)。
發現如果矩形不包含一個整的邊的話,每個矩形中至少有一個點可以用\(2-SAT\)來表示。
可以用\(2-SAT\)解決這一種情況了。
T3
簡要題意:一開始你有\(m\)個二維平面上的點\((x_i, y_i)\)。\(x_i + y_i \leq n\)
然后有\(4\)種操作,
1.詢問標號為\(P\)的點的橫縱坐標
2.對於所有\(y_i \leq L\)的點,\(x_i \leftarrow \max (x_i, n - L)\)。
3.對於所有\(x_i \leq L\)的點,\(y_i \leftarrow max(y_i, n - L)\)
4.增加一個點\((X, Y)\),它的標號是之前所有出現的點的個數 \(+ 1\)。
時限:\(11 s\)
數據范圍:\(m \leq 500000, q \leq 10^6\)。
考場上這題寫了\(22\)分。
\(1\)分算法:
對於\(m \leq 2000, q \leq 5000\)的測試點,我們直接暴力\(O(mq + q^2)\)的求解。
只有\(1,2,4\)操作時:
考慮把問題轉換為求一段時間區間里面的\(l \ge x\)的所有\(l\)的最小值。我們可以用可持久化線段樹來實現。
只有\(1,2, 3\)且\(x\)非嚴格遞增,\(y\)非嚴格遞減時:
考慮操作過程中永遠滿足\(x\)非嚴格遞增,\(y\)非嚴格遞減。
所以每次操作影響的標號是一段區間,且影響為\(y\)坐標賦為某個值,或者\(x\)坐標賦為某個值。可以在線段樹上維護
\(x\)的最小值,\(x\)的最大值,\(y\)的最小值,\(y\)的最大值,\(x\)的賦值標記,\(y\)的賦值標記這六個變量來實現線段樹上二分找區間以及線段樹上修改的操作。
我們終於獲得了\(22\)分。
滿分做法
首先考慮\(65\)分做法,即沒有插入操作。注意到所有被至少修改一次的節點中,如果\(x_i < x_j\),那么\(y_i \ge y_j\),證明很顯然。
上述結論等價於將所有節點按\(x\)關鍵字排序,\(y\)為第二關鍵字排序時,所有節點\(y\)坐標從左往右非嚴格單調遞增。
每次修改相當於修改\(x \leq a, y \leq b\)的點,根據以上結論,其實等價於按如下方式排序時,修改一段連續的區間。
我們對每個節點使用動態開點線段樹 + 二分計算出每個節點最先修改的時間。然后我們在平衡樹在對應的時間中支持加入節點和區間修改。
上述結論對新加入的節點不成立,於是我們可以利用二進制分組做進行定期的重構,這樣每個節點至多被重構\(log\)次,每次修改只要對\(log\)棵平衡樹進行修改,復雜度為\(O(n \log^2 n)\)
JOISC 2020 Round2 自閉記
T1
\(O(n^2)\)算法:
考慮詢問任意一對點\((u, v)\),如果答案為\(1\)的話,得到\(u\)顏色與\(v\)相同或$u \space loves \space v \(或\)v \space loves \space u$。
接下來我們考慮如何區分這三類邊。
對一個點\(u\),如果\((u, v_1), (u, v_2), (u, v_3)\)是我們得到的點,那么我們詢問\((u, v_1, v_2), (u, v_1, v_3), (u, v_2, v_3)\),會得到其中一組的答案是\(1\)。設為\((u, v_1, v_2)\)。我們可以得到\(u \space loves \space v_3\)。如果\(u\)度數為\(1\),那么和它相鄰的點與它顏色相同。
這樣我們可以得到所有的有向邊,於是剩下的邊就是滿足端點顏色相同的邊。
可以獲得\(40\)分。
4分算法
問題變成詢問一個點集里面出現不同顏色個數。
我們可以考慮二分,對每個點\(u\),設剩下的點是\(v_1, ...., v_{2n - 1}\)。
詢問\((u, v_1, ..., v_i)\),再詢問\((v_1, ..., v_i)\)。
如果第一個詢問的答案比第二個多,說明\(u\)的顏色和\((v_1, .., v_i)\)是不同的。
這樣我們可以二分出來和\(u\)的顏色相同的點。
Subtask 4 算法
如果我們知道每只變色龍的性別?
我們考慮對一個雄性的點\(u\),設雌性的點為\(v_1, ..., v_n\),那么我們詢問:
\((u, v_1, ..., v_i)\),如果答案是\(i + 1\),那么就知道\(u\)到\(v_1, ..., v_i\)沒有邊。
我們可以通過這個事實依次二分出\(3\)條\(O(n^2)\)算法所要知道的邊。
再使用\(O(n^2)\)算法的后半部分即可。
通過長途跋涉我們獲得了\(64\)分。
某個\(O(n \log n)\)算法:
目前此算法的常數較大且不太容易優化,我們先咕着不講吧。
By zbw
另外一個:
By wyp
我們考慮將所有有顏色相等和love關系的點連邊構成一個圖\(G\)。
首先我們發現如果\(S\)是獨立集,且\(u\)不在\(S\)中。
當且僅當Query \(S \cup \{ u \}\) 的,答案為\(\lvert S \rvert + 1\),\(S \cup \{ u \}\)的並集也是獨立集。
我們考慮在所有的點中,隨機一個順序,然后從左往右如果能加進獨立集就加,會得到一個比較大的獨立集。
(\(\ge 點數/4\),可以算兩次,發現每個不在獨立集的點都與獨立集連了至少一條邊,且每個點的度數\(\leq 3\))。
把所有不在獨立集的點\(u\)去向獨立集這個序列二分,會得到所有不在獨立集的點連向在獨立集的點的邊。
然后把這個較大的獨立集刪掉。不斷這樣子做,直到最后所有點都刪除過。
下同\(O(n^2)\)的后半部分。
需要注意常數。
T2
維護所有雙向邊構成的連通塊。
我們發現要維護的東西有:
1.對每個連通塊\(i\),維護一個\(set\) ,\(from_i\),所有連向它的點。
2.對每個點\(i\),維護一個\(set\),\(to1_i\),表示它連向的連通塊的集合。
3.對每個連通塊\(i\),維護一個\(set\),\(to2_i\),表示它連向的連通塊。
4.對每個連通塊\(i\),維護一個\(set\),\(comp_i\),表示它內部的點數。
維護一個隊列,表示可能需要縮的兩個連通塊。
每次在成功縮連通塊后,
1.更新答案。
2.把增加的可能要縮的連通塊\(push\)進隊列。
連通塊合並的過程我們可以用啟發式合並來獲得更優的復雜度。
關鍵代碼如下:
void merge (int u, int v) {
int ru = findroot(u), rv = findroot(v);
if (ru == rv) return ;
if (!to1[u].count(rv)) {
to1[u].insert(rv), to2[ru].insert(rv);
from[rv].insert(u), ans += sz[rv];
}
if (!to2[rv].count(ru)) return ;
ans -= 1ll * sz[ru] * from[ru].size();
ans -= 1ll * sz[rv] * from[rv].size();
ans -= 1ll * sz[ru] * (sz[ru] - 1) / 2;
ans -= 1ll * sz[rv] * (sz[rv] - 1) / 2;
if (sz[ru] > sz[rv]) swap(ru, rv);
for (set<int> :: iterator it = comp[ru].begin(); it != comp[ru].end(); it++) {
int x = *it;
comp[rv].insert(x), from[rv].erase(x);
for (set<int> :: iterator i = to1[x].begin(); i != to1[x].end(); i++) q.push(make_pair(x, *i));
}
comp[ru].clear();
for (set<int> :: iterator it = from[ru].begin(); it != from[ru].end(); it++) {
int x = *it, rx = findroot(x);
to1[x].erase(ru), to2[rv].erase(ru);
if (rx != rv) {
to1[x].insert(rv), to2[rx].insert(rv);
from[rv].insert(x);
q.push(make_pair(x, rv));
}
}
from[ru].clear();
for (set<int> :: iterator it = to2[ru].begin(); it != to2[ru].end(); it++) {
int x = *it;
if (x != rv) to2[rv].insert(*it);
}
to2[ru].clear();
ans += 1ll * (sz[ru] + sz[rv]) * from[rv].size();
ans += 1ll * (sz[ru] + sz[rv]) * (sz[ru] + sz[rv] - 1) / 2;
fa[ru] = rv, sz[rv] += sz[ru];
}
for (int i = 1; i <= m; i++) {
int u, v;
read(u), read(v);
q.push(make_pair(u, v));
while (!q.empty()) {
pair<int, int> pi = q.front();
q.pop(), merge(pi.first, pi.second);
}
write(ans), putchar('\n');
}
若使用\(hash\)表,復雜度可以做到\(O((n + m) \log n)\)。否則為\(O((n + m) \log^2 n)\)。可以獲得\(100\)分。
T3
考慮給定\({a}\),原序列等價於每次將序列\({a}\)中最大的兩個值\(u, v\),將它們加入集合\(S\),並pop出最大的那個值。考慮所有pop出的值即為最后剩余的石柱。
考慮換一種方式理解,其實等價於下標從大到小枚舉所有石柱,找到最大的正整數\(x\)使得\(x \leq a_i\),\(x \ge 1\)且\(x\)未被占據,那么將\(x\)占據。
根據以上方式進行dp,設\(f_{i, j}\)表示處理了后\(i\)個下標的石柱,前\(j\)個高度被占據的方案數。根據以上位置是否被占據進行轉移。
若當前位置被刪除,則可以直接計算出方案數。否則枚舉以下新增的連續段長度進行轉移。
JOISC2020 Round3 自閉記
T1
\(O(n^2)\)算法:
把問題轉換為放代價和最大的黃點,使得每個沒有白點的子矩形最多有一個黃點。
考慮先建出來笛卡爾樹,然后設\(f_{u, i}\)表示在橫坐標\(u\)的子樹代表的區間,且在\(A_u\)上邊的黃點的縱坐標為\(i\)時,所有黃點的最大收益。設\(g_u\)表示在\(A_u\)上面沒有黃點時這一塊黃點的最大收益。
轉移時我們需要枚舉最上面的黃點的橫坐標在哪一塊。(在左子樹/\(u\)/右子樹)。
可以獲得\(35\)分。
\(O((n + m) \log n)\)算法:
考慮用線段樹合並優化之前的算法。
DP優化 -> 把其中一維看成函數/序列,用數據結構維護這一維的變化。
我們把DP的第二維看成序列。
發現轉移的變化可以用區間加/區間詢問max/兩個線段樹代表的數組取max這三種操作來實現。
於是我們用一個標記永久化的線段樹即可通過此題。
T2
考慮第\(i\)個人摘了當前蘋果后,下一個摘當前蘋果的人是誰,可以連出一個\(n\)條邊\(n\)個點的有向圖。顯然是由一個基環內向樹和一些鏈組成。只需要利用數據結構在圖上進行模擬題的操作,即可計算出答案。難點在代碼的實現。
T3
前\(4\)個Subtask
我們考慮按深度\(\mod 3\)染色。
先對整張圖做BFS,求出每個點的深度。
然后對於每條邊\((u, v)\),這條邊染\(\min(dep_u, dep_v) \mod 3\)
考慮第二個程序怎么寫?
假設她當前所在的點深度為\(d\),那么和它相連的邊中要么標號是\(d \bmod 3\),要么是\((d - 1) \mod 3\)
且至少有一條是\((d - 1) \mod 3\)。
如果所有邊標號是相同的,就返回那個標號。
否則標號是有\((d - 1) \mod 3\),也有\(d \mod 3\)。我們可以唯一確定哪一個是\((d - 1) \mod 3\)。
可以通過\(15\)分。
鏈的特殊做法
對這條鏈循環染\(110010\)。我們第一步先隨便找一個方向走。記錄之前走過的顏色。如果我們走\(3\)步過后發現這三步走過的顏色只能是從上往下走走出來的顏色串,然后我們就再往回走\(3\)步即可。
樹的特殊做法
Subtask 5
我們考慮按深度\(\bmod 2\)染色。對每條邊\((u,v)\)染\(\min(dep_u, dep_v) \bmod 2\)。
所有數據
我們考慮先將樹划分成若干條鏈,如果一個點有兩個兒子,就稱它為分叉點。我們希望染色里面這種條件能滿足:
1.每個分叉點中到父親的邊和到兒子的邊全部不相同
2.每一條鏈上都是\(110010110010....\)的循環移位。
第二個條件的目的是讓它不同方向的長度為\(5\)的子串是不一樣的。
第二個程序的人:
她第一步可以記錄\(2\)條邊的顏色,如果她一直往下走了\(3\)步並且即將走第\(4\)步的時候發現她看到的\(5\)條邊只能是從上往下走形成的\(5\)條邊,那么她就開始不斷往回走。否則她繼續往前走。
如果她走到分叉點,那么她可以判斷出來父親的邊,朝着它往上走即可。
如果她走到非分叉點,我們需要判斷她走下一條邊后是否是走在錯誤的方向上。如果是,就往回走,否則繼續往下走。
細節比較多。
本人考場上\(71\)分部分分的代碼如下:
int Move (vector<int> y) {
bool flag[2] = {false, false};
if (first) {
for (int i = 0; i < 2; i++) {
if (y[i]) {
first = false, last_go = i;
info += (i == 0 ? "0" : "1");
return i;
}
}
}
else {
int edge_cnt = y[0] + y[1];
if (edge_cnt == 0) {
first_fail = true;
return -1;
}
if (edge_cnt == 1) {
if (y[0]) {
if (first_fail) {
last_go = 0;
return 0;
}
else {
info += "0";
if (se.count(info)) {
first_fail = true;
return -1;
}
last_go = 0;
return 0;
}
}
else {
if (first_fail) {
last_go = 1;
return 1;
}
else {
info += "1";
if (se.count(info)) {
first_fail = true;
return -1;
}
last_go = 1;
return 1;
}
}
}
else {
if (y[0] > 1 && !y[1]) {
first_fail = true;
return -1;
}
if (y[1] > 1 && !y[0]) {
first_fail = true;
return -1;
}
last_go ^= 1;
return last_go;
}
}
}
JOISC Round 4自閉記
T1
有一棵樹,每個點有\(k\)種顏色中的一種。現在你要把一種顏色\(y\)的點全部變成顏色\(x\)。稱這種操作為合並操作。問你最少要多少次合並操作才能選出來一種顏色使得它們構成樹上的一個連通的子圖。
\(O(n \log n)\) 算法
把題意稍微轉化為選出一個顏色的集合使得擁有這些顏色的點構成連通的子圖。
考慮點分治,每次設分治重心為\(u\),那么我們求出來連通塊包含\(u\)的答案。
考慮建一張圖,以\(u\)為根,若一個點\(v\)的父親是\(fa_v\)。則我們將\(v\)的顏色與\(fa_v\)連邊。然后從\(u\)的顏色開始沿着這張圖\(dfs\)一遍,即可判斷出來最少要多少種顏色才能構成連通子圖了。
注意我們每次分治完了過后要把跨過\(u\)的兩個子樹以及\(u\)所染的顏色標為不合法。
在之后\(dfs\)中如果發現必須要選這種顏色,就不更新答案了。
即可做到\(O(n \log n)\)。
T2
本人在考場上拿了\(36\)分。以下是我\(36\)分的亂搞。
第一步:對所有白點,如果存在唯一的方向使得可以以白點為中心,選出一個PWG/GWP的一串的話,我們則將P和G連邊。跑二分圖最大匹配。
第二步:我們在這個解的基礎上做爬山。如果我們再發現有白點可以以它為中心選一串的話,就繼續選。
第三步:我們再枚舉一個白點,然后改變它的方向,然后再看它附近的白點有沒有能夠再選一串的。
我們可以通過這個亂搞獲得\(36\)分。
T3
本人考場上只拿了\(9\)分。
4分算法
對於\(T_i = 0\)的點,轉換為選一些區間覆蓋\([1,n]\)的所有點。離散化后優化\(dp\)即可。
5分算法
枚舉每個治療方案是否被選擇,然后維護被感染的人組成的段即可模擬出在執行這些治療方案后是否所有人都被治愈。
35/100算法
令對於第\(i\)個方案,\(f_i\)表示在\(t_i\)天,\([1, r_i]\)的人都康復的最小代價。那么考慮轉移,\(i\)能轉移到\(j\)當且僅當\(l_j \leq r_i - \lvert t_i - t_j \rvert + 1\)。可以轉換成圖的模型,跑最短路。利用數據結構優化連邊,復雜度為\(O(m \log^2 m)\)。