001. CF2002E Cosmic Rays CF*2300
標籤:思維,棧
題意:
給定 \(n\) 個元組,\((a_i,b_i)\),表示有 \(a_i\) 個 \(b_i\) 按順序排列在一起。一次操作可以刪除以下數字:
- 在第 \(1\) 個位置的數字
- \(s_i ≠ s_{i-1}\) 的位置 \(i\)
問每個字首最多成操作多少次。
Observation:
- 問每個字首最多能撐住幾輪操作,其實暗示我們利用前面的答案以某種方式推出後面的答案。
- 手玩樣例可以發現,如果對於 \(x\) 個元組,\(b_i\) 全都不相同,那麼答案即為 \(max(a_1,a_2,...,a_x)\)。證明也是比較顯然的。
- 我們考慮處理權值相同的情況,我們簡化問題進行討論——只考慮兩個權值相同的塊夾著一個不同權值塊的情況。將三個塊的大小設為 \(x,y,z\)。如果 \(z≤y\),那麼根本不會有任何貢獻,答案還是前面的。如果 \(z>y\) 且 \(x≤y\),那麼其實就會加上這個塊的貢獻,不過這種情況我們在程式碼中其實是可以直接壓進去不管的。如果 \(x>y\) 且 \(z>y\),那麼就會把 \(y\) 消了,然後 \(x\) 和 \(z\) 拼在一起。然後這個串可以撐住的輪數就變為了 \(x+z-y\)。
- 兩個權值相同夾著中間的塊不管是什麼,我們只需要知道它能撐住的輪數即是它的 \(y\)。
Doubt:
想到這裡就感覺想不下去了。當時的想法是因為看到標籤裡有 dp,看怎麼樣能找到前面相同的權值然後用資料結構最佳化一下。結果解法是棧,有些震驚。
Solution:(after seeing le0n's tutorial)
有點像單調棧的意思。
- 如果前面的塊大小比你現在壓進來的小,那他根本就沒用,直接更新一下現在消了的次數然後直接從棧裡踢出去即可。
- 如果權值和自己相同,更新自己的大小為 \(a_i+stk_{top}-maxx\),這根據的是性質 \(3\)。然後把這個塊踢出去,為什麼呢?因為你自己合併之後還會壓進來更大的塊。
- 其他情況就不能吞或合併了,因為人家比你的大小大,你影響不了人家。
- 把自己這個更新後的元組壓進棧,最大值更新,做完了。
002. CF906D Power Tower CF*2700
標籤:擴充套件尤拉定理
雖然 2700 但不怎麼難的題。
擴充套件尤拉定理降冪塔的另一大板子(第一個是上帝與集合的正確用法)。
題意:
給定陣列 \(a\),每次給定區間 \([l,r]\),詢問 \(a_l^{{a_{l+1}}^{...^{a_r}}}\)。
Observation:
- 看到冪塔,想到擴充套件尤拉定理+快速冪的光速冪解法。
- \(\varphi\) 函式的層數是 \(logp\) 級別的,也就是說每次詢問的複雜度都是雙 \(log\)(有一層快速冪)。而 \(\varphi\) 變為 \(1\) 後上面的也不用管了。(上一個板子的結論)
- 發現需要用到的 \(\varphi\) 值就那麼幾個,完全可以根號 \(n\) 單點查,或者提前預處理(推薦這個)
Solution:
- 預處理出要使用的模數
- 遞迴處理詢問(DFS)
- 快速冪略有不同,大於 \(p\) 需要模 \(p\) 再加 \(p\)。
- 暴力計算即可
003. CF242E XOR on Segment CF*2000
標籤:線段樹,位運算
題意:
在一個序列上做兩種操作
- 詢問區間和
- 區間異或 \(x\)
Solution:
比較經典的一個 trick 吧。關於區間位運算的,就考慮狀壓線段樹或者拆位線段樹。這裡拆位線段樹比較好寫,於是就寫了空間開銷較大的拆位。
對於數的每一位維護一顆線段樹。
- 於是對於修改操作——異或 \(x\)。就是把 \(x\) 拆成二進位制數,如果 \(x\) 這一位為 \(0\),那麼就沒有改變,否則在區間線段樹上,這一位全部取反。而其和的變化就是 \(sum\) 變為了 \(r-l+1-sum\)。懶標記記錄這個區間是否翻轉即可,下傳操作是顯然的。
- 對於求和,顯然的,就是\[\sum^{log(maxn)}_{i=0} Sum(i,l,r) \times 2^{i} \]
另外注意建樹的時候,每一位都要 PushUp 就好。
004. CF438D The Child and Sequence CF*2300
標籤:線段樹,勢能分析
題意:
在一個序列上做三種操作:
- 詢問區間和
- 區間對 \(x\) 取模
- 單點修改
Observation:
- 發現一個勢能性質就好,就是區間取模次數是 \(log(a_i)\) 級別的,於是進行類似 GSS4 的暴力單點改就好
- 什麼時候不改?顯然的,區間最大值小於 \(x\) 就不改。
Solution:
- 對於操作 \(1\),維護區間 \(sum\)。
- 對於操作 \(2\),如 observation 所提到的,進行單點暴力修改。
- 對於操作 \(3\),暴力修改。
- 記得 PushUp,但因為沒有懶標記,所以不用 PushDown。
005. CF434D Water Tree CF*2100
標籤:線段樹,樹鏈剖分
題意:
給出一棵以 \(1\) 為根節點的 \(n\) 個節點的有根樹。每個點有一個權值,初始為 \(0\)。\(m\) 次操作。操作有 \(3\) 種:
- 將點 \(u\) 和其子樹上的所有節點的權值改為 \(1\)。
- 將點 \(u\) 到 \(1\) 的路徑上的所有節點的權值改為 \(0\)。
- 詢問點 \(u\) 的權值。
Solution:
就,就直接樹鏈剖分。注意處理 \(1\) 和 \(0\) 的優先順序。如果沒有 tag 記得設為 \(-1\) 而不是 \(0\),因為會造成歧義。
點查詢的話,其實維護區間和就好,並沒有必要為了點查詢進行額外的麻煩操作。
區間改 \(0\) 就把 tag 和 sum 都賦值為 \(0\)。區間改 \(1\),就把和賦值為 \(r-l+1\) 即可。
006. CF600E Lomsat gelral CF*2300
標籤:線段樹合併
題意:
有一棵 \(n\) 個結點的以 \(1\) 號結點為根的有根樹。每個結點都有一個顏色,顏色是以編號表示的,\(i\) 號結點的顏色編號為 \(c_i\)。如果一種顏色在以 \(x\) 為根的子樹內出現次數最多,稱其在以 \(x\) 為根的子樹中占主導地位。顯然,同一子樹中可能有多種顏色占主導地位。你的任務是對於每一個 \(i∈[1,n]\),求出以 \(i\) 為根的子樹中,占主導地位的顏色的編號和。
Solution:
考慮對每一個節點建一棵權值線段樹,而每次算貢獻其實就是將自己節點的權值線段樹向自己的父親合併。而權值線段樹所要維護的就是出現次數的最大值和最終的答案。
如何計算這個節點總的答案?很簡單,就是 \(sgt_{root_u}.ans\),因為最終的所有答案都會合併到以 \(root_u\) 為根節點的線段樹上。
唯一要注意的細節就是權值線段樹的合併問題,還有加點較為麻煩,更新部分可以考慮加一個 PushUp 函式減小碼量。
007. UVA437 The Tower of Babylon 提高+/省選-
標籤:拆點,動態規劃
看紫書看到這麼個題,於是就混進來一道 Uva。
題意:
有 \(n\) 種長方體,邊長為 \(a,b,c\)。可以任意調轉長方體的方向。現在進行長方體堆疊,滿足 \(a'<a\) 且 \(b'<b\) 的長方體可以堆疊在邊長為 \(a,b,c\) 的長方體上面。問最高能堆疊到什麼高度。
Solution:
其實還是挺簡單的,首先由於可以翻轉,考慮把 \(a,b,c\) 按任意順序排列,拆為 \(6\) 個點(好像 \(2\) 個就夠,但是 \(6\) 個好想)。隨後對這 \(6n\) 個點按題目要求建圖。
注意到題目中是天然的二維偏序關係,於是這個圖是一個 有向無環圖。在這個 DAG 上面跑 Toposort 求出最長路即可。
最後答案是所有的 \(dis\) 取一個 \(max\)。
008. CF1209G1 Into Blocks (easy version) CF*2000
標籤:並查集,STL-set
題意:
給定序列 \(A\)。每次改動需要花費 \(cnt_x\) 的代價將所有權值為 \(x\) 的位置全都改換為另一個權值 \(y\)(自己定)。最終要求所有相同的數都連續出現。問最小代價。
Observation:
- 我們發現,相交的區間和不交的區間有本質區別。於是先想到維護每個權值的左右端點。
- 在一群相交的線段裡面,所花費的最小代價就是區間長度減去數最多的一個權值的個數。
- 把所有貢獻加起來就是答案。
Solution:
- 考慮經典 Trick,使用 set 的神奇排序維護區間交,並使用 dsu 並查集把交的線段放一個集合裡。
- 怎樣維護區間求交?詳見會場預約那題。大概就是:
bool operator < (const Line &x) const
{
return R < x.L;
}
然後如果交就會判定為相等,但是其實有點細節。比如合併的這段程式碼:
int minn = ll[i], maxn = rr[i];
while(pos != s.end())
{
Union(i, (*pos).id);
minn = min(minn, (*pos).L);
maxn = max(maxn, (*pos).R);
s.erase(pos);
pos = s.find(Line{ll[i], rr[i], cnt[i], i});
}
s.insert(Line{minn, maxn, cnt[i], i});
要維護 \(minn\) 和 \(maxn\) 是因為,有可能前面的線段和你有交,和後面的線段也有交,而唯獨和你沒交,於是因為你把它們刪了,於是就不可能合併到一個集合了,所以說,你得維護目前的最小左和最大右端點才能維護交。
- 後面就是暴力統計就行。
這做法比較 naive,貌似還有更好的做法。
009. P3071 [USACO13JAN] Seating G 省選/NOI-(真實難度 提高+/省選-)
標籤:線段樹,區間合併線段樹
題意:
有一排 \(n\) 個座位,\(m\) 次操作。\(A\) 操作:將 \(a\) 名客人安置到最左的連續 \(a\) 個空位中,沒有則不操作。\(L\) 操作:\([a,b]\) 的客人離開。
求 \(A\) 操作的失敗次數。
Solution:
如果不是連續的,也有另外一種做法,就是二分 + 線段樹,這種要求連續的更難一些。
其實與 GSS3 區間最大子段和非常像甚至還弱化了。在一個 \(01\) 序列上做操作。對於線段樹上每一個節點 \(p\),維護其最長 \(1\) 連續段 \(anscnt\),包含左端點的最長 \(1\) 連續段 \(lcnt\),包含右端點的最長 \(1\) 連續段 \(rcnt\)。另外記錄一個 tag 維護區間推平為 \(0/1\)。
線段樹的 PushUp 是難點,但是被 GSS3 完美覆蓋了,所以直接上程式碼:
if(Sgt[lc(p)].anscnt == Sgt[lc(p)].R - Sgt[lc(p)].L + 1) Sgt[p].Lcnt = Sgt[lc(p)].anscnt + Sgt[rc(p)].Lcnt;
else Sgt[p].Lcnt = Sgt[lc(p)].Lcnt;
if(Sgt[rc(p)].anscnt == Sgt[rc(p)].R - Sgt[rc(p)].L + 1) Sgt[p].Rcnt = Sgt[rc(p)].anscnt + Sgt[lc(p)].Rcnt;
else Sgt[p].Rcnt = Sgt[rc(p)].Rcnt;
Sgt[p].anscnt = max(max(Sgt[lc(p)].anscnt, Sgt[rc(p)].anscnt), Sgt[lc(p)].Rcnt + Sgt[rc(p)].Lcnt);
建樹和更新的時候細節都有些多,需要想清楚。
下一步是求連續區間左端點。
- 如果其左子樹的最長 \(1\) 連續段已經大於等於要求,那麼優先遞迴左子樹(要求編號最小)。
- 如果恰好跨過中間,也就是左子樹的 \(rcnt\) 加上右子樹的 \(lcnt\) 是大於等於要求的話,那麼就返回 \(mid\) 減左子樹的 \(rcnt\) 再加 \(1\),這很好理解。
- 否則就是在右邊,遞迴右子樹。
複雜度?這樣只會遞迴一邊,複雜度是必定有保證的。是一種 類似線段樹二分 的方法。
有個雙倍經驗是 Hotel G。
010. P2962 [USACO09NOV] Lights G
標籤:高斯消元
題意:
給出一張 \(n\) 個點 \(m\) 條邊的無向圖,每個點的初始狀態都為 \(0\)。
你可以操作任意一個點,操作結束後該點以及所有與該點相鄰的點的狀態都會改變,由 \(0\) 變成 \(1\) 或由 \(1\) 變成 \(0\)。
你需要求出最少的操作次數,使得在所有操作完成之後所有 \(n\) 個點的狀態都是 \(1\)。
Solution:
看到 \(n \leq 35\),考慮亂搞。注意到每個點的“變化操作”相當於一個係數只有 \(0/1\) 的 \(n\) 元方程。而對於每一個元 \(i\),我們要讓其值為 \(1\)。
因為我們考慮且僅考慮其在模 \(2\) 意義下的值,所以我們可以使用 異或高斯消元。
最後如果沒有自由元的話答案就是 \(1\) 的個數,有自由元的話暴力搜尋即可。注意此時其實不用折半搜尋,因為自由元的個數理論上是不會超過一半的,搜尋回溯複雜度可以接受。