十一雜題選講 - Virtual Judge (vjudge.net)
/mnt/e/codes/contests/20241008/sol.md
- [AGC004F] Namori
- [1406E] Deleting Numbers
- [1081G] Mergesort Strikes Back
- [1033E] Hidden Bipartite Graph
- [1254E] Send Tree to Charlie
- [1012E] Cycle sort
- [1284F] New Year and Social Network
- [1292E] Rin and The Unknown Flower
- [1548D2] Gregor and the Odd Cows (Hard)
[AGC004F] Namori
[AGC004F] Namori - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
最重要的觀察是:將
- 所有點初始顏色為白。
- 每次操作可以處理一條邊,其兩個點如果顏色相同則都變成相反的顏色。
- 請使所有點的顏色反轉。
轉寫為
- 找一棵生成樹,做二分圖黑白染色。
- 對於二分圖上的邊,每次交換兩個點的顏色。
- 對於非二分圖上的邊,其兩個點如果顏色相同則都變成相反的顏色。
- 請使所有點的顏色反轉。
本題不在二分圖上的邊最多一條。剩餘的討論可以直接看 題解 AT2046 【AGC004F Namori】 - 洛谷專欄 (luogu.com.cn)。
啟發:操作中帶有“如果”是不好刻畫的,像本題的操作可以透過黑白染色,將其轉化為交換操作。
[1406E] Deleting Numbers
Deleting Numbers - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
這個的話就直接對每個質因子考慮即可了。第一個質因子使用根號分治找出,然後就能一次詢問判斷 \(x\) 是否有其他質因子。質因子的次數使用二分求出。
[1081G] Mergesort Strikes Back
Mergesort Strikes Back - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
葉子上的貢獻十分好求。然後發現現在合併就是將一個數與那個位置的字首最大值綁在一起考慮了。
對著字首最大值考慮很難。聽課之後發現,可以直接對每個位置上的數考慮。固定 \(lhs\) 的第 \(i\) 個數和 \(rhs\) 的第 \(j\) 個數。設 \(lhs\) 並 \(rhs\) 的最大值為 \(mx\),若 \(mx\in\{lhs_i, rhs_j\}\),則它們一定會被排好序。否則它們的順序變為固定的,有 \(1/2\) 的機率貢獻逆序對。
啟發:拆貢獻。
[1033E] Hidden Bipartite Graph
Hidden Bipartite Graph - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
這個以為很難,沒仔細想。首先肯定是搜出一棵生成樹。如果我們能很快找出一條存在的邊,那麼我們只需要將這個過程 perform \(n-1\) 次就能找出一棵生成樹。然後可以判斷二分圖的某一部內有沒有互相連邊,隨意二分一下用 \(O(\log n)\) 次代價。所以怎麼找出存在的邊,不妨寫 bfs,就是對於一個點 \(u\) 和未訪問點集 \(S\)(沒有必要重複搜所有的點),詢問 \(\{u\}\cup S\) 減去 \(S\) 的答案就能知道有沒有邊,於是就能 \(O(\log n)\) 找出這條存在的邊。還有邊可以繼續找,反正該過程不會超過 \(O(n)\) 次。
[1254E] Send Tree to Charlie
Send Tree to Charlie - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
本題首先觀察到我們肯定是將有限制的 \(a_i\) 往目標點移動,到目標點的路徑上會有對一個點連線的邊中欽定某一條要最先操作,或者欽定某一條最後操作,或者欽定某一條在另一條之後操作。然後還觀察到如果路徑總和超過 \(2n\)(或者說很多次經過某個點)肯定是無解的。
聽完課發現因為我們要數的是最終 \(a_i\) 形態而不是操作序列數,所以每個點上都應獨立考慮。那麼在一個點上,它相連的邊有若干順序限制,只在這個點上考慮限制,若每個點上都有解,則我們隨便搞都能構造方案(例如,先選一條邊,然後一直追溯它的前驅,然後操作,具體細節不重要,並注意到這可能對應多個方案但只會對應一種最終序列)。而每個點上因為是描述”緊挨“的限制,所以只會有一大堆鏈和一大堆非法的環。然後判掉一種情況之後,每個點的方案數就變成了一個階乘。全部點的答案乘起來就是答案。
寫程式碼的時候發現一種有趣的寫法。atexit
函式。在全域性開一個存答案的變數,初始為 \(0\),然後在 main
的第一行呼叫 atexit(+[]() { cout << ans << endl; });
,這樣在判到無解之後直接 exit(0)
就能輸出答案。有解的情況就修改 ans
,正常退出的時候也會輸出答案。
啟示:注意對最終序列計數和對操作序列計數的差異。前者可能是刻畫最終序列上每個點的性質,每個點之間可能是獨立的。後者可能是刻畫操作的性質,或它們之間的順序與關聯。兩者不能混淆。
[1012E] Cycle sort
Cycle sort - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
P6305 [eJOI2018] 迴圈排序 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
一開始發現序列有重複就擺爛了,沒往下做下去。但實際上可以
cin >> n >> S;
for (int i = 1; i <= n; i++) cin >> a[i], hua << a[i];
hua.build();
for (int i = 1; i <= n; i++) a[i] = b[i] = (int)hua(a[i]); // 0-indexed
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++) if (a[i] != b[i]) g[a[i]].emplace_back(b[i], i);
先離散化 \(a_i\),然後複製一份到 \(b_i\),給 \(b_i\) 排序,然後 \(a_i\) 向 \(b_i\) 連邊。然後求所有連通分量各自的尤拉回路,也可以達到和排列置換環一樣的效果。
然後是原題怎麼做,一開始還讀錯題了導致啥都不會,實際上做法是:丟掉長度為 \(1\) 的置換環後,將所有置換環分成兩部分,一部分置換環各自做一次 cycle sort 結束(注意已經是置換環了,只用換一次);另一部分首先將它們全部拍平到一個操作序列上輸出做 cycle sort,這時每個置換環都有一個數字飛出去,另一個數字飛進來。只需要再用一次操作將它們反向彈飛即可,這樣操作次數大大減少。顯然只有 \(O(\text{置換環個數})\) 種本質不同的操作序列,列舉一下看看誰是答案。
啟示:置換環相關的題目,如果發現有重複元素,不妨求尤拉回路。
[1284F] New Year and Social Network
New Year and Social Network - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
這題首先要觀察到答案必定為 \(n-1\) 可太草了,冷靜一下發現 Hall 定理直接滿足。……