標題黨,事實是只有除了和 flights 和 device2 兩道通信題之外的 \(10\) 道傳統題。
所有題面、下發樣例、Ranking 和我自己寫的代碼全部都在:
鏈接:https://pan.baidu.com/s/1YxqHtAI38EnBATcvaxyk5g?pwd=hyfl
提取碼:hyfl
百度網盤,狗都不用
對你可能有用的鏈接:JOI Spring Camp 2022 Online Contest
Day 1
jail
正向地記錄這道題是怎么想到的。首先樹上問題有一些一般的套路,可能會想到一些按子樹搞,或者每次拿兩條路徑判一判之類的,但想想也知道不靠譜。然后手玩一下第二個樣例,看一看 NO
是怎么來的,其實能隱約感覺到有 “成環” 的意思,這時候就能想到建有向圖判強連通性或者 2-sat 之類,但是剛才說了每次拿兩條路徑是不行的,所以不能 2-sat。
就算猜到了要 Tarjan 但是歸納不出來條件也是白瞎的,所以就先拿個鏈的情況來分析一下怎么回事。一看如果兩個方向相反的交叉了肯定不行,如果方向相同的一個包含另一個也不行,但是如果方向相同的只是交叉沒有包含(好像)暫時又沒事。試圖把這些條件搬到樹上,感覺非常不充分,而且對方向相同的沒啥約束力。
直接考慮硬上,從有向邊的意義的角度考慮可能會有什么樣的邊。其實從常理來考慮可能要建很多很多變量表示每條路徑經過每個點的時間會比較充分,但這看起來太冗雜不可能是正解。所以干脆破罐破摔,只考慮路徑與路徑之間的約束關系,並且注意到這對應一個結論可以一次把一條路徑移動完再移動下一條路徑,這個結論看起來還算比較靠譜,就當它是真的。發現一條路徑 \((s, t)\),如果有另一條路徑經過了 \(s\),它就要比這條路徑后移動;如果它經過了 \(t\),它就要比這條路徑先移動。這個條件非常簡潔優美,讓人陡增信心。
考慮在 DAG 上表示出這些邊,只需要把這條路徑代表的點連向路徑上的 \(t\),把路徑上的 \(s\) 都連向這條路徑。這需要實現一個點向樹上路徑連邊和一個區間向一個點連邊,用數據結構來優化它,樹剖之后相當於一個點向一個區間連邊,用線段樹優化建圖就好了。
kyoto
首先這道題有一個比較淺顯的結論:只會用到在前綴 \(\min\) 或后綴 \(\min\) 處的 \(A_i, B_j\)。略證如下:考慮這樣的一條路線
B_1 B_2 B_3
A_1 ------|
A_2 |______
考慮 \(A_1, A_2\) 中的較小者,如果 \(B_2 > B_1\) 且 \(B_2 > B_3\),那么在走左下和走右上兩條路線中一定有一條不劣於這條路線,就可以把 \(B_2\) 扔了。這個結論導出一個 40pts 的做法,因為有用的 \(A_i, B_j\) 個數是值域級別的。
進一步考慮上面這張圖更一般的情況,設五個值分別為 \(A_i, A_{i + x}\),\(B_{j - y_0}, B_j, B_{j + y_1}\),那么左下的路線和當前路線的差是 \(x(B_1 - B_2) - y_0(A_1 - A_2)\),右上路線和當前路線的差是 \(x(B_3 - B_2) - y_1(A_2 - A_1)\),只有這兩者都大於等於 \(0\) 的時候 \(B_2\) 才有可能有用。它們分別是 \((B_1 - B_2, y_0) \times (A_1 - A_2, x)\) 和 \(-(B_2 - B_3, y_1) \times (A_1 - A_2, x)\) 這就是說 \((B_1 - B_2, y_0)\) 要在 \((A_1 - A_2, x)\) 的順時針,\((B_2 - B_3, y_1)\) 要在 \((A_1 - A_2, x)\) 的逆時針。又因為 \(x, y_0, y_1 > 0\),這三個向量在一個半平面內,所以 \((B_1 - B_2, y_0)\) 要在 \((B_2 - B_3, y_1)\) 的順時針。所以 \((j, B_j)\) 必須在 \((j - y_0, B_{j - y_0}), (j + y_1, B_{j + y_1})\) 連線的下方,進而所有可用的 \((j, B_j)\) 都在下凸殼上,對 \(A\) 也同理。
然后我們來考慮這兩個凸包,注意到上面這兩條折線相當於在 \(B\) 的凸包上的三個點 \((j - y_0, B_{j - y_0}) \to (j, B_j) \to (j + y_1, B_{y + y_1})\) 形成的兩段折線當中插入了一個 \((i, A_i) \to (i + x, A_{i + x})\),就因為這段折線在(順逆時針)的方向上位於那兩段折線之間。所以最優解一定形如在兩個凸包上走,每次挑選下一段折線方向更適宜的那個凸包走一個,而且這可以導出唯一的解,因此這么做就是最優的。而且因為現在 \(A\) 和 \(B\) 都是凸包,所以一定能做到每一段折線都按順序排,說白點就是凸包的閔可夫斯基和。
misspelling
考慮一堆約束的本質,相當於欽定了 \(s[l, r - 1]\) 和 \(s[l + 1, r]\) 的大小關系。進一步來說這就是要求 \(s[l]\) 開始的一段極長的相等字符之后接的第一個不相等的字符是比它更大或比它更小。字符集比較小,考慮 \(f(i, j)\) 表示 \(i\) 開始的后綴,\(s[i] = j\) 的方案數。考慮轉移,如果下一個不和 \(s[i]\) 相等的位置在 \(k\),不妨設它比 \(j\) 大,那么所有左端點在 \([i, k)\) 之間的區間,要求比 \(s[i]\) 小的區間的右端點必須在 \(k\) 左邊,也就是不能有一個區間穿過 \(k\)。隨着 \(i\) 往左移動,相當於每次會把一段(從 \(i\) 開始的)前綴的 \(k\) 刪掉。所以,用一個堆維護目前還合法的 \(k\) 就行了。
Day 2
copypaste3
區間 dp,記 \(f(l, r)\) 表示這個區間的最小代價,手打對應的轉移是 \(f(l, r) \to f(l, r + 1)\)。主要考察不斷做剪切 - 粘貼這個操作有什么性質,發現它可以把 \(s\) 變成 \(A_1sA_2s\cdots sA_k\) 這種結構,其中 \(A_1, A_2, \cdots, A_k\) 是在兩次粘貼之間手打進去的字符。首先我們考慮扔了 \(A_1\),記一個 \(g(l, r)\) 表示最后一次操作是粘貼的情形下的最小代價,那么 \(g(l, r)\) 可以往 \(g(l - 1, r)\) 轉移,解決了 \(A_1\),這樣 \(s\) 一定是從 \(l\) 開始的一個子串。首先如果選擇粘貼 \(s\),我們一定會粘貼它的連續一段出現,中間不選擇手打。注意到,因為 \(s\) 有長度,所以對一個 \(l\) 來說合法的 \((s, 粘貼次數)\) 對是調和級數的。那么如果選擇的 \(s\) 的最后一次粘貼的右端點在 \(k\) 的位置,我們就可以轉移到 \(g(l, r \geq k)\),並且代價關於 \(r\) 是一次項系數固定的一次函數(因為只有手打的代價 \(Cr\)),也就是我們用假設全部手打的代價減去所有 \(s\) 的長度之和,再加上復制粘貼這些 \(s\) 的代價。所以只希望常數項越小越好。打一個后綴標記再一遍前綴和過去就行了。這樣就完成了 \(g\) 的轉移,把 \(g\) 轉移到 \(f\) 也是不費功夫的事。
flights
通信題咕了。
team
按照 \(x\) 掃描,那么另外兩個 \(y, z\) 要構成逆序對,並且不得比 \(x\) 最大的這個的 \(y, z\) 小。我們在 \(x\) 排第二的這個人處把這個逆序對插入到數據結構里,那如果它要當 \(z\) 最大的,選的那個就肯定選 \(y\) 最大(但是 \(z\) 比它小)的。用兩個 BIT 維護 \((y, z), (z, y)\) 的前綴最大值,用一個 BIT 套線段樹維護 \((y, z)\) 逆序對的最大和。
Day 3
device2 (80pts)
關鍵在於這樣一個序列:\(B = [+1, -1, +1, -1, \cdots, +1, -1]\),它的前綴和是 \(+1 / 0\)。
如果考慮要傳遞的信息的前綴和,我們發現這個序列對前綴和只有 \(1\) 的擾動,那是否我們讓要傳遞的信息前綴和全部 \(\times 2\) 就行了呢?這樣我們可以知道只有和之前一個前綴和絕對值相差至少 \(2\) 的才是我們想要的,但是面臨一個問題,舉個例子上一次的前綴和是 \(2k\),這回的前綴和是 \(2k - 2\),那么這中間如果被擾動一下,得到 \(2k - 1\),也就是模 \(2\) 余 \(0\) 和模 \(2\) 余 \(1\) 都是原來的前綴和可能達到的值,我們分辨不出來。
如果我們考慮讓前綴和全部 \(\times 3\),就可以解決這個問題,因為 \(3k - 3\) 被擾動會變成 \(3k - 3, 3k - 2\),那么模 \(3\) 余 \(2\) 是永遠不會被達到的一個值,這樣就可以分辨出來合法的前綴和。這樣需要花費 \(180\) 個 bit。
sprinkler
為了這玩意兒第一次寫邊分,也算是難忘經歷。“點分 \(1\text{s}\) 就想到了對吧”(jt 語),然后發現因為不支持除法導致不能先用總的做再除以分治中心各個子樹內部的;又不能前綴后綴掃描,那樣要多帶 \(\log\)。考慮邊分,這樣只有兩個子樹(也就是一般人們說的邊分在度數相關問題上有優勢),考慮相互之間的貢獻,按照深度 two-pointers,用一個 \(40\) 的數組記錄對應深度的貢獻積就行了。邊分之前要三度化(左兒子右兄弟就蠻不錯的),虛邊的長度需要設為 \(0\)。
但其實存在聰明而且低復雜度做法。考慮兩點 \((u, v)\) 距離如果是一個和 \(d\) 奇偶性相同的數(而且比 \(d\) 小),那么一定存在一個公共祖先使得 \(u, v\) 分別到這個祖先距離之和恰好是 \(d\)(或者爬到根沒祖先了),而奇偶性和 \(d\) 不同也同理一定存在一個祖先到兩者距離和是 \(d - 1\),所以只需要在這些祖先的地方統計答案,每個祖先開個桶記錄 \(40\) 個深度對應的個數就行了。
sugar
我們實際上要求的是二分圖的最大匹配,不妨考慮轉化成求最大獨立集。求最大獨立集就意味着任意兩個選擇的 \(A, B\) 相距都要超過 \(L\),注意到這時候我們一定有如果選擇了一個位置的 \(A\) 就一定選擇這個位置的所有 \(A\),\(B\) 也一樣,所以現在只關心每個位置選不選了。
我們肯定會用一棵線段樹來維護決策最大獨立集的過程,但是如果決策每個位置選不選,就比較吃癟,因為這樣 dp 必然要涉及到記錄上一個選擇的位置之類的東西,是很不好維護的。於是我們考慮先選出所有的 \(A\),然后排除掉在這些 \(A\) 鄰域內的所有的 \(B\). 不能選擇的 \(B\) 是每個選中的 \(A\) 為中心半徑為 \(L\) 的區間的並覆蓋到的位置。
接下來是比較騷的操作,如果有兩個半徑為 \(L\) 的這樣的區間交了,那么我們不妨把中間交起來的地方里面的 \(A\) 也選上,這樣徒增了 \(A\) 但是 \(B\) 沒有變(因為長為 \(L\) 的區間的並沒有變)。所以最終選擇的 \(A\) 一定形如一堆連續區間,而且每個連續區間里 \(A\) 的半徑為 \(L\) 的區間的並是不交的。這個並的右端點由這個連續區間里最右的 \(A\) 決定,左端點由連續區間里最左的 \(A\) 決定,這個連續區間設它最左的 \(A\) 選在 \(l\),最右的 \(A\) 選在 \(r\),它的貢獻是:
讓兩個括號分別是 \(f_r\) 和 \(g_l\),所以我們相當於要選一堆 \(f\) 和 \(g\) 交替使得和最大,\(l\) 可以等於 \(r\) 。這是一個比較方便用 dp 解決的問題,但是題目的修改操作來者不善。
考察 \(A_i\) 單點加 \(x\) 會產生什么樣的影響:
- 對 \(j \geq i\),\(f_j := f_j + x\)
- 對 \(j \geq i + 1\),\(g_j := g_j + x\)
考察 \(B_i\) 單點加 \(x\) 會產生什么樣的影響:
- 對 \(j \geq i - L\),\(f_j := f_j - x\)
- 對 \(j \geq i + L + 1\),\(g_j := g_j - x\)
我們注意到對於 \(A_i\) 的操作,它等價於在 \([i + 1, n]\) 這段區間內對 \(f, g\) 同時區間加,在這個范圍內它只跟 \(f, g\) 的個數差有關,而這個個數差只跟 dp 記錄的端點選擇的是 \(f\) 還是 \(g\) 有關(因為 \(f, g\) 是交替的);然后又在 \(i\) 這個位置對 \(f\) 單點加,這是容易處理的。
然后再看 \(B_i\) 的操作,用類似的方法分析的話,相當於對 \([i + L + 1, n]\) 這段區間內對 \(f, g\) 同時區間加;對 \([i - L, i + L]\) 這段區間只對 \(f\) 加。這個就比較難受。因為選擇的個數和 \(f\) 的個數相關。但是,我們已經說了對於 \([l_i, r_i]\) 和 \([l_{i + 1}, r_{i + 1}]\) 有 \(r_i + 2L < l_{i + 1}\),也就是說這段 \([i - L, i + L]\) 內的 \(l_i\) 就最多有一個了。
現在我們記 \(dp(s, t, k)\) 表示這段區間內最左選中的是 \(s \in \{0, 1\}\),最右選中的是 \(t \in \{0, 1\}\),是否(\(k \in \{0, 1\}\))有選中至少一個 \(l_i\) 的情況下的最大值。
這道題還有一個模擬網絡流的做法,用霍爾定理(其實就是點數減最大獨立集的特殊形式)可以判斷是否存在完美匹配。二分圖匹配增廣有一個性質:加入新點時,已經在匹配中的點不會退出匹配,只會改變點之間匹配的方式。每次加入新點之后相當於想要讓這些新點和已經存在的點中還沒匹配的匹配,把它們欽定到匹配中去,並且使得被欽定的點集確實存在一個完美匹配,這樣就代表新點確實可以和目前選擇的這些匹配。處於貪心的角度考慮我們一定會試圖讓新點和能匹配的點中最近的匹配。然后考慮怎么判斷一個點集是否存在完美匹配,其實就是對應上面做法中每一個選取 \(g, f, g, f, \cdots\) 的方案之和都要小於等於 \(0\)(對應霍爾定理的 \(|X_0| \geq \Gamma(X_0)\)),這等價於每一對選擇 \((g, f)\) 的方案都小於等於 \(0\)。可以直接用線段樹維護逆序對的方式來處理這個,轉移的時候就看一看左兒子里 \(g\) 的最值和右兒子里 \(f\) 的最值。
Day 4
dango3
一言難盡。。。考慮每次找出一串,一個好想法是每次二分(找一串復雜度 \(O(n (\log_2 nm))\))出一個顏色的最后一次出現(我們可以通過刪掉一個前綴是否還能組成和全集組成個數一樣的串來判斷是否刪掉了某個顏色的所有出現),然后它輕松地突破了上界。我把它和暴力結合,也就是剩下的數量比較少的時候,線性地找,這樣需要五萬八次。我考慮分塊,每 \(B\) 個一塊,如果這一塊里有不合法的,就挨個找,否則跳過這個塊,這樣復雜度是 \(O(m(\dfrac{nm}B + nB))\),這樣粗略分析當然是在 \(B = 5\) 的時候最優。但是實際上造組數據 \(B = 5\) 五萬二,\(B = 9\) 四萬七。。。然后交上去並沒有通過,整了一組顏色分布比較整齊的數據,八萬多。。。猛回頭,把一開始的掃描順序 shuffle 一下,就過了。
一個據說被廣泛采用的做法:掃描的時候維護若干不包含重復元素的串,加入的時候先從小的開始判斷。
一個真的很優異的做法:定義 \(\texttt{solve(S, m)}\) 表示你想的這個意思,然后掃描過程中維護一個每種顏色都不超過 \(\dfrac{m}{2}\) 次出現的極大集合,這樣掃到頭每種顏色應該恰好出現 \(\dfrac{m}{2}\) 次,分治下去。
fish2
每個元素在吃掉一些相鄰的元素之后權值會變成這段區間里元素的和,所以能吃掉的極長區間相等的元素是等價的。用線段樹維護在當前這個區間內吃,每個極長區間對應的魚的數量。注意到兩點:第一點是我們只關心那些至少能吃到左端點或者右端點的區間;第二點是如果一個元素能吃掉一個前綴而恰無法吃掉下一個,說明這段前綴的和還沒有下一個元素大,前綴和在這個位置就會翻倍,所以每個區間最多有 \(O(\log V)\) 個這樣的區間。
現在我們只關心怎么合並兩個節點。我們注意到要合並的有這樣一種單調性,即子節點較長的前綴在父節點里會變成的區間一定包含較短的前綴會變成的區間。但我們不希望直接愚蠢地模擬這個過程,因為我們不希望看到 \(O(\)區間長度\()\) 的合並。但是比如一個已經存在於右子節點的前綴,往左吃然后卡住,的事情也只會發生在前綴和翻倍的位置。所以我們不妨記錄每個前綴和翻倍的位置對應的前綴有多少個元素能吃到這個前綴,然后每次都暴力判所有前綴和翻倍的位置,中間帶一個 2-pointers 稍微優化一下。
這樣合並節點的時候就要關心前綴和翻倍的位置怎么合並,它顯然是兩邊候選位置的並的一個子集,帶着判一下就行了。
reconstruction
這個問題就是邊權是一次函數的最小生成樹在 \(q\) 個點處的點值查詢。能夠感受到這個問題具有一定的凸性,每條邊存在的時間是一個區間。我們現在來考慮怎么求出每條邊存在時間的區間。先假設我們在一個詢問時刻 \(t\) 非常小的時候,那我們相當於求的是最小生成樹,那什么時候這個生成樹會改變呢?其實就是如果有一條生成樹外的邊滿足它比鏈上一條邊的權值小了,這條邊就會替換那條邊。。。(這題解寫成智障了屬於是,“那什么時候生成樹會改變呢?小編也不知道,小編也很好奇”)所以在鏈上最大的那條邊和這條邊權值的平均數的時刻,這條邊會替換那條邊。
如果我們能找出這樣替換的事件,按時間發生的順序,那是再好不過,但這個比較難做到。如果把所有邊按照從小到大排序,這會導致替換的事件不是按時間順序被一一找到。好比說現在樹上有兩條邊 \(1, 4\),用 \(5\) 在時刻 \(5.5\) 能替換 \(4\),用 \(6\) 在 \(3.5\) 時刻能替換 \(1\),如果先加入 \(5\) 再加入 \(6\),那么時刻就亂了。但這實際上沒關系,因為邊的替換是這樣點對點的,我們只要保證被替換的邊永遠是被最早替換它的替換掉就行了,而這正只需要用替換它的邊的權值排序。
由於 \(n\) 比較小,我們可以暴力維護這棵樹,每次只需要暴力 dfs 找到鏈上最大邊。然后要做的是一個對詢問的區間加一次函數,用前綴和維護就行了。但是如果想偷懶,因為 \(n\) 比較小,所以每次遍歷生成樹上所有邊也是可以的。。。