1. 程式人生 > 其它 >JOISC 2021 部分題解

JOISC 2021 部分題解

為了讓題解集合不顯得太臃腫,程式碼託管到了第三方上面。

Day1

參考程式碼

特技飛行

Libre OJ 連結

又是提答又是計算幾何,註定很少人會去寫它

IOI 熱病

Libre OJ 連結

咕掉惹。

飲食區

Libre OJ 連結

考慮如果我們可以去除“離開”操作的影響,假裝沒有人離開並修改“服務”時候的查詢引數,那麼也可以解決問題。

假如沒有人離開,那麼就可以使用整體二分解決這個問題。

現在有人離開,假如不存在某次佇列全部清空的情況,那麼可以直接讓詢問的 \(B\) 加上離開人數從而去掉離開的影響。

假如存在清空的情況,那我們需要統計實際離開人數。假設對於商店 \(A\),目前有 \(n\)

次人數變化,它第 \(k\) 次人數變化量為 \(\delta_k\)(使用正負標註“離開”或“加入”),那麼當前實際人數為:

\[\begin{aligned} &\max_{0\le j\le n}\left\{\sum_{k=j+1}^n\delta_l\right\}\\ =&\sum_{j=1}^n\delta_j-\min_{0\le j\le n}\left\{\sum_{k=1}^j\delta_k\right\} \end{aligned} \]

由於 \(\delta\) 是按照時間排序的,因此可以按照時間,用線段樹維護每個位置上人數變化的字首和最小值。知道了實際人數,也就不難算出“服務”的具體引數了。

Day2

參考程式碼

逃跑路線

目前只能在 JOISC 官網上面下載題目檔案。OJ 上還沒有測評包。

道路建設*

Libre OJ 連結

鑑於 \(K\) 比較小,因此可以想到用堆維護每個點的最近點對,並按順序取出前 \(K\) 小的點對。

為了保證不會重複計算,對於標號為 \(i,j(i<j)\) 的點,我們只會在 \(j\) 上計算 \((i,j)\) 的點對。

一個比較**的做法是,將曼哈頓距離轉化為切比雪夫距離,那麼查詢最近點對可以使用二分+二位數點,可惜是 \(O(n\log_2^2n)\) 的。

另一個更加厲害的做法是,直接用 KDT 維護最近點對!這個我不清楚啊,看別人寫的

真正的做法:

由於計算點對已經有了有序性,利用這個有序性我們可以順便拆掉一個絕對值。比如,將所有點按照 \(x\) 排序就可以去掉 \(x\) 上的絕對值。那麼我們只需要處理 \(y\) 一維的絕對值,這個按 \(y_1,y_2\) 關係分類後利用線段樹完成。

考慮到每次加入點和每次刪除點對都只會修改一個位置,我們可以使用可持久化線段樹維護字首可用點。

刪除需要注意。最值本身不支援直接刪除,但考慮線段樹的葉子上已經保證了 \(y\) 相等,而 \(x\) 又是從小到大有序的,所以對於一個葉子而言,該位置上的前一個版本的資訊一定就是刪除該點對之後的最大值,所以讀取前一個版本的資訊即可。

小結:

  1. 看到“絕對值”這樣的資訊,應該要注意分類討論,而非盲目地轉化成切比雪夫距離;
  2. 主席樹刪除用到的小技巧可以注意一下。

購物

目前只能在 JOISC 官網上面下載題目檔案。OJ 上還沒有測評包。

Day3

參考程式碼

古老的機器*

UOJ 連結

人生中第一道通訊題,獻給了 JOISC 2021 的這道題目

30 pts

首先需要知道如何不得到 Wrong Answer [6]

考慮最左邊的一個 \(X\),如果移除順序合理,它是可以被共享的。

將該 \(X\) 之後的序列按照 \(Z\) 分段,並且分段處理,每一段在處理完之後需要保證完全被移除。那麼,每一段上就只可能有 \(X\) 或者 \(Y\)。對於連續的一段 \(Y\),選取其中的任意一個留在最後移除效果都一樣;而對於連續的一段 \(X\),我們可以讓它們在右邊的機器全部被移除之後再移除它們,一定是最優的。

為了簡化操作過程,我們從左往右移除段,每一段上先從右往左逆向移除 \(X,Y\),最後再將 \(Z\) 刪除。可以發現這個過程是符合上面的分析的。

Anna 只需要每兩位描述一個機器的型號,將資訊傳給 Bruno,剩下的交給 Bruno 就可以獲得該部分分。

現在的空間利用比為 1 : 2。

40 pts

由於每一位只有 \(3\) 種情況,使用 \(2^2\) 的狀態描述太浪費了。

注意到 \(\log_2 3^n=\log_2 2^{n\log_2 3}\approx1.6n\),因此我們可以代入得到 \(3^5\) 應該與 \(2^8\) 接近。

實際上也確實如此,\(3^5\) 略小於 \(2^8\),因此我們可以用 8 位二進位制壓縮 5 位資訊。只需要利用 \(1.6n\) 位即可完成任務。

現在的空間利用比為 5 : 8。

70 pts

Bruno 實際上只需要知道第一個 \(X\) 的位置和之後的 \(Z\) 各自的位置就可以知道如何操作。我們可以將這些位置用 1 標註,這樣他就可以自己識別了。

現在的空間利用比為 1 : 1。

100 pts

這個優化就比較厲害了

考慮到連續的一段 \(Z\) 實際上意義也不大,除了最右端的 \(Z\) 以外前面的 \(Z\) 也沒啥用。那麼我們就可以只在最後的這個 \(Z\) 上做記號。

這樣的傳輸資訊有啥特點呢?很重要的性質就是除了第一個 \(X\) 與第一個 \(Z\) 以外,剩餘的 1 不可能相鄰

這樣的序列的數量是多少呢?通過各種方法可以算出,長度為 \(n\) 且無相鄰 1 的序列數量為 \(f_{n+2}\),其中 \(f_k\) 為 Fibonacci 數列的第 \(k\) 項。

嘛,\(f_{n+2}\) 肯定還是會比 \(2^n\) 小一些的。打表觀察又可以發現 \(f_{63+2}\) 略小於 \(2^{44}\),因此我們可以選擇用 44 位二進位制壓縮 63 位資訊,那麼只需要使用約 \(\frac{44}{63}n\) 即可解決問題。

有一個小細節,為了避免第一個 \(X\) 與第一個 \(Z\) 的 1 連續,我們可以在第一個 \(X\) 之後插入一個 0 來處理。這樣基本不會影響整體操作。

現在的空間利用比為 63 : 44。

小結:

  1. 壓縮資訊的通訊題目,目的就在於提高空間利用比。

    對於 Anna 而言,作為資訊發出端,她需要做的是:

    • 讓同等長度內的字串裝下儘可能多的資訊

      例如,從 1 : 2 提升到 5 : 8 的過程中,原先的 \(3^4\) 裝進 \(2^8\),顯然沒有 \(3^5\) 裝進 \(2^8\) 划算。

      由於字串的資訊通常是指數級的,因此這通常是在函式上找整點的問題。

    • 通過優秀的編碼方式來表示資訊

      例如,從 1 : 1 提升到 63 : 44 的過程中,已有的 \(2^n\) 對於有效資訊來說還是浪費;給有效資訊合理編號從而壓縮標號的值域才能成功地減少所需長度.

    對於 Bruno 而言,作為資訊接收端兼操作端,他需要做的是:

    • 明確如何操作。

    • 簡化操作從而簡化需要的資訊。

      同樣是 1 : 1 提升到 63 : 44 的過程中,部分 \(Z\) 的資訊已經被省略掉了,才能相應地減少有效資訊。

  2. 任何有意義資訊的基礎都是有效編碼。在二進位制字串中,可用碼很少,因此需要充分利用字串的附加資訊,例如編碼格式。所以在實際操作過程中一定需要注意設計好格式。對於字串而言,字串內容是明面資訊,而字串格式更是重要的隱含資訊。

保鏢

Libre OJ 連結

又咕掉惹

聚會 2

Libre OJ 連結

為了方便虛樹,下面稱居住點數目為 \(k\) 時的答案為 \(f_k\)

以出席者的居住點構建虛樹,那麼滿足條件的開會點就是樹上帶權重心

如果帶權重心是一個點那沒什麼可說的;如果是一條邊 \((u,v)\),那麼滿足條件的點的數量為 \((u,v)\) 兩點在原樹上的距離 +1

什麼時候重心在邊上?當然是這條邊左右子樹大小一樣的時候。回到原樹上來,如果 \((u,v)\) 想要成為虛樹上的重心,那麼就必須存在一種方案使得以路徑 \((u,v)\) 為根的時候\(u\) 的子樹內和 \(v\) 的子樹內可以選出同樣多的居住點來。

設此時 \(u\) 子樹大小為 \(s_u\)。上述條件意味著 \((u,v)\) 可以貢獻到 \(f_{2k}\),當且僅當 \(k\le \min\{s_u,s_v\}\)

那麼問題就方便多了。對於 \((u,v)\),我們可以將它貢獻到 \(g_{\min\{s_u,s_v\}}\) 去,那麼 \(f\) 就可以由 \(g\)\(O(n)\) 的時間內計算出來。

此外,由於 \(u\) 的子樹大小取決於 \(u,v\) 之間的相對關係,我們需要分類討論:

  • 如果 \(u,v\) 之間存在祖孫關係:

    這個時候不妨設 \(u\)\(v\) 的祖先,唯一需要注意的是 \(u\) 的子樹大小會發生改變。

    我們在 \(s_u\) 成為較小值的時候統計一遍 \((u,v)\) 的資訊,此時是按照 \(s\) 的大小查詢子樹資訊;

    我們還在 \(s_v\) 成為較小值的時候統計一遍 \((u,v)\) 的資訊,此時可以逆序列舉 \(s\) 並進行祖先查詢;

  • 如果 \(u,v\) 之間不存在祖孫關係:

    那麼 \(u,v\) 的子樹大小都不會有變化。

    可以在 \(\operatorname{LCA}(u,v)\) 處統計資訊。那麼就不難想到設 \(dp_{u,i}\) 表示 \(u\) 的子樹大小為 \(i\) 的子孫的最大深度。

    轉移可以直接線段樹合併,貢獻相當於做了一次 \(\min\) 卷積,維護一下字尾最大即可。

    注意:線段樹合併的時候,遇到了空結點需要將已有的字尾資訊在非空結點上打上“貢獻”標記,避免漏掉貢獻。

小結:

  1. 一定要想清楚之後再開始寫程式碼,不要急急忙忙導致反覆重構
  2. 線段樹合併的時候不要漏掉了貢獻。

Day4

參考程式碼

活動參觀 2*

Libre OJ 連結

求字典序最小不難想到從小到大列舉每個活動是否能加入到“必選”活動中。

如何檢查?考慮當前活動時間為 \([l,r]\),如果它不與其他必選活動相交,那麼它就會被包含在空閒時間段 \([L,R]\) 中,也即 \([l,r]\subseteq [L,R]\)。此時 \([L,R]\) 會被拆分成兩個新的時間段 \([L,l]\)\([R,r]\),因此我們只需要能夠快速求出任一空閒時間段的最大活動數即可。

一般我們會採用 \(O(n)\) 的貪心處理。但是,貪心一般意味著一些較簡單可疊加的操作。例如,詢問空閒區間 \([p,q]\) 時,我們會在可用活動中選取右端點最小的,例如 \([a,b]\),之後的操作就相當於繼續對 \([b,q]\) 進行詢問。

由於 \([a,b]\) 的選擇與 \(p\) 有較大關聯,因此我們可以設 \(f_p\) 為滿足 \(p\le a\) 的活動中的最小的 \(b\)。那麼詢問就相當於反覆跳 \(f_p\),因而可以使用倍增優化。於是單次查詢就變為了 \(O(\log_2n)\)

小結:

  1. 對於倍增的適用範圍要足夠熟悉,它通常是處理一些相似、重複的行動,例如指標跳轉。

    由於部分貪心操作相對簡單,倍增也常常可以用於優化貪心

  2. 應當分析一些貪心操作的本質;

嚮導 2

目前只能在 JOISC 官網上面下載題目檔案。OJ 上還沒有測評包。

最差記者 4

Libre OJ 連結

草系列題目怎麼都出到 4 了

考慮從 \(i\)\(A_i\) 連邊,最後一定可以得到一棵基環內向樹。

這裡列舉一些簡單的性質:

  1. 根據邊的含義可以知道環上的點最終 rating 相等;
  2. 修改後的 rating 總可以是初始 rating 之一,因此初始 rating 是可以離散化的;
  3. 我們可以反過來求沒有修改的選手的 \(C\) 之和的最大值,下文稱“沒有修改的選手的 \(C\) 的和”為“貢獻”;

考慮特殊的樹的情況:樹上就可以 DP 了,比如設 \(f_{u,i}\) 表示當 \(u\) 的 rating 為 \(i\) 時的最大貢獻。

可以目測出 \(f\) 的轉移為:

\[f_{u,i}=[i=H_u]C_u+\sum_{v}\max_{i\le j} f_{v,j} \]

如果暴力做是 \(O(n^2)\) 的。

注意到轉移中複雜的地方就是 \(\max_{i\le j} f_{v,j}\),如果我們能快速維護它就好了。

為此,我們設 \(g_{u,i}=\max_{i\le j}f_{u,j}\),並研究一下它的性質:

  1. \(g_{u,i}\)\(i\) 增大而不增;

  2. \(g_{u,i}\)分段的;

  3. 如果將 \(g_{u,i}\)\(g_{v,i}\) 對應位置加起來:

    其中的藍色實線表示 \(g_v\),紅色虛線表示 \(g_u\),紫色點線表示 \(g_u+g_v\)

    可以發現 \(g_u\)\(g_v\)差分也是對應相加的,並且與一般的 \(g\) 一樣也具有相似形式。

  4. 在轉移中,\(\sum_{v}g_{v,i}\) 是具有相似形式的,我們考察在 \(H_u\) 的位置單點加上 \(C_u\) 之後對 \(g_u\) 的影響:

    可以發現此時是從 \(H_u\) 開始向前,將一段推平。如果考慮 \(g_u\) 的差分,那麼相當於找到最大的 \(l\),使得 \([l,H_u)\) 的差分之和大於 \(C_u\),之後便會將 \((l,H_u)\) 的差分推為 0,並且修改 \(l,H_u\) 的差分值。

通過上述分析,我們可以發現 \(g\) 的差分可以很方便地使用線段樹維護。

這便是樹上的解法;基環樹上也並不複雜。將環上的每棵樹都做一遍 DP 之後合併。由於這裡的 \(f\) 並沒有做字尾 \(\max\),所以需要列舉最終環上的權值並計算最大貢獻。

時間複雜度可以是 \(O(n\log_2^2n)\),也可以是 \(O(n\log_2n)\)

小結:

  1. 運用函式思維、幾何方法,分析 DP 陣列的性質從而利用資料結構高效維護;
  2. 補集轉化的思想,大大簡化了問題;