數據結構亂寫


loj6515 貪玩藍月
容易發現本題中要求的信息不支持快速合並,不支持快速刪除,但是支持快速插入。
所以一個簡單的離線做法就是線段樹分治。
只要按照時間建線段樹,把每個操作插入到對應節點上。
最后 \(dfs\) 一遍線段樹順便插入,在葉子節點輸出答案即可。
然而這個信息是支持快速合並兩個信息的。
即可以將問題分為兩半,之后枚舉一邊大小,另外一邊通過單調隊列來實現找到最大值。
所以只要取 \(n\) 個元素的中點為分界點。
一個問題是,如果 \(pop\) 的過多導致超出分界點應該如何處理。
方法是繼續選擇另一邊的中點為分界點,對全局進行暴力重構。
直觀理解復雜度就是對的,可以通過分析這樣一個函數來證明其復雜度:\(\phi=分界點左元素個數-分界點右元素個數\)
對於每個操作,可能會使其變化 \(+1,-1\),單次操作復雜度為 \(O(p)\)
另一種可能是使其賦值為 \(0\),因為此時左側或右側元素個數為 \(0\),單次操作復雜度就是 \(O(|\phi| p)\)
因為在操作之后將函數值歸零,復雜度是正確的。

CF1129D Isolation
容易想到一個簡單的朴素 \(dp\)
\(dp_i\) 表示當前將 \([1,i]\) 划分為了若干合法段的方案數。
有這樣的轉移 \(dp_i=\sum \limits_{j=0}^{i-1} dp_j * [f(j+1,i) \leq k]\)
其中 \(f(l,r)\),表示 \([l,r]\) 這段區間中出現次數恰好為 \(1\) 的顏色數。
然后發現 \(f\) 函數沒有什么很好的性質,並不能用數據結構簡單的維護。
考慮由 \(f(l,r-1)\)\(f(l,r)\) 的變化。
那么對於 \(a_r\) 出現的最后三個位置 \(x,y,r\)
\([x+1,y]\) 內不再出現恰好一次,進行區間減法。
\([y+1,r]\) 內恰好出現一次,進行區間加法。
對於其他情況的取值,沒有影響。
所以考慮對原序列分塊,每個塊開一個桶記錄每個 \(f\) 值對應 \(dp\) 的和。
對於每次修改操作重構邊界塊,對內部的整塊移動一下指針即可。

loj 3166 魔法樹
其實限制的是這樣一個東西,每個點的選擇時間要小於等於其父節點的選擇時間。
若一個點 \(i\) 的選擇時間恰好為 \(d_i\),則獲得 \(w_i\) 的收益,求最大收益。
可以進行一個 \(dp\)\(dp_{i,j}\) 表示 \(i\) 的選擇時間為 \(j\) 的最大收益。
令設 \(g_{i,j}=\max \limits_{k=1}^j \{dp_{i,k}\}=\max \{g_{i,j-1},dp_{i,j}\}\) 輔助轉移。
那么 \(dp_{i,j}=[j=d_i]*w_i+\sum \limits_{x\in son_i} g_{x,j}\)
容易發現一個點有效的 \(dp\) 值只有子樹中出現過的選擇時間。
根據樹上啟發式合並的思想可以繼承重兒子,暴力累加輕兒子。
得到 \(dp\) 數組,然而仍然無法暴力掃一遍進行取 \(\max\) 操作。
然而其實我們只需要 \(g\) 數組,然后前綴取 \(\max\) 這個操作具有推平的性質。
所以用一個 \(set\) 維護每一個連續段的 \(g\) 值,在合並的過程中跑一跑就可以做。
更簡單的做法是對 \(g\) 數組進行一下差分,只維護其中非 \(0\) 的位置。
然后子樹合並是簡單的,對於單點修改操作,顯然並不能直接累加在差分數組上。
執行下面這樣的操作來彈掉錯的部分即可。

    s[x][d[x]]+=w[x]; ll delta=w[x]; auto it=s[x].upper_bound(d[x]),tmp=it;
    while(it!=s[x].end())
        if(delta>=it->second) delta-=it->second,tmp=it++,s[x].erase(tmp);//dp(x,d(x))更優
        else{ it->second-=delta; break; }//這個點更優

loj 2537 Minimax
首先是一個簡單的 \(dp\)
\(dp_{i,j}\) 表示節點 \(i\) 權值為 \(j\) 的概率。
注意到每個葉子節點的權值不同,對於 \(j\) 在左子樹中:
\(dp_{i,j}=dp_{lch,j}*(p_i \sum \limits_{k=1}^{j-1} dp_{rch,k}+(1-p_i) \sum \limits_{k=j+1}^{\infty}dp_{rch,k})\)
對於 \(j\) 在右子樹中是同理的。
對於一個節點,\(dp_i\) 中不為 \(0\) 的下標一定是子樹中出現過的權值。
所以使用線段樹合並來優化這個轉移。
對於當前合並到的節點 \(lc,rc\),區間為 \([l,r]\)
\(lc,rc\) 均有值,那么遞歸兩個兒子節點。
若只有 \(lc\) 有值,實際上只需要得知 \(\sum \limits_{k=1}^{l-1} dp_{rc,k}\)\(\sum \limits_{k=r+1}^{\infty} dp_{rc,k}\),然后打上區間乘法標記即可。
這兩個信息只要維護子樹和,就可以在線段樹合並的同時維護出來。

CF1340F Nastya and CBS
首先對原序列分塊,依次考慮每個塊。
若塊內的括號無法進行匹配,顯然括號序列非法。
否則可能存在一些無法匹配的括號,這些括號一定可以表示為 \())))(((\)
對於詢問操作,從左到右依次考慮每個塊(包括散點)。
維護一個棧表示已有的左括號。
然而一個問題是,如果暴力加入每個塊中的括號,復雜度顯然是錯的。
對於棧內元素維護一下元素個數和元素的所屬塊,首先確定彈出的元素個數,然后通過哈希判斷是否可以一次性彈出。

CF1290E Cartesian Tree
根據笛卡爾樹的性質,一個子樹的 \(size_i\) 實際上就等於 \(r_i-l_i+1\),(這里的 \(l_i,r_i\) 是動態變化的,所以應該是排名)。
這個信息可以拆成 \(l_i,r_i\) 分別維護,考慮后者。
對於 \(r_i\) 這個東西,實際上就是滿足 \(a_{r_i+1}>a_i\) 的最小的 \(r\)
考慮插入一個最大值 \(a_{p}=x\),排名為 \(q\)\(r_i\) 的影響。
對於滿足 \(r_i \geq q\) 需要進行 \(+1\) 操作,因為插入了一個數導致排名產生變化。
對於 \(i<p\) 的下標 \(i\),需要使 \(r_i\)\(q-1\)\(\min\)
注意到前一個操作似乎並不能簡單維護,但是將兩個操作結合起來卻可以。
對於 \(i>p\),有 \(r_i \geq i > p\)
對於 \(i<p\),顯然 \(r_i\) 在進行過第二個操作之后 \(<q\)
所以問題就是前綴取 \(\min\),后綴 \(+1\),全局求和。
這個東西寫個勢能分析的線段樹,維護 最大值/次大值/最大值出現次數/兩個加法標記/區間和/區間合法點數 就好了。

CF1260F Colored Tree
考慮通過枚舉顏色/點對來求和。
\(g_i=r_i-l_i+1\),這樣的話答案可以寫為
\(\sum \limits_{c} \sum \limits_{l_i\leq c \leq r_i} \sum \limits_{i<j,l_j\leq c \leq r_j} \frac{dis(i,j)}{g_ig_j} \prod \limits_{k=1}^n g_k\)
\(dis(i,j)\) 拆成 \(dep_i+dep_j-2dep_{lca(i,j)}\),這樣的話可以將式子中的 \(i,j\) 分裂開求和。
考慮通過類似掃描線的算法,掃一遍顏色 \([1,m]\)
同時動態加入符合條件的,動態刪除不符合條件的 \(i\)
要求的是 \(\sum \limits_{i} \frac{dep_i}{g_i} \sum \limits_{j>i} \frac{1}{g_j}\)
\(\sum \limits_{i} \frac{1}{g_i} \sum \limits_{j>i} \frac{dep_j}{g_j}\)
\(\sum \limits_{i} \frac{1}{g_i} \sum \limits_{j>i} \frac{dep_{lca(i,j)}}{g_j}\)
前兩項都可以簡單的維護,最后一項要用到一個特殊的技巧。
\(dep_{lca(i,j)}\) 這個東西差分到樹上的每個節點。
其實就是把每個 \(i\) 都打到它的每個祖先節點上,貢獻為 \(\frac{1}{g_i}\)
對於每個 \(j\),對它的祖先節點權值求和即可。
區間修改/區間查詢,可以用樹剖+樹狀數組/線段樹維護,也可以用 \(lct\)

loj 3277 星座 3
對樓房高度建出笛卡爾樹。
這樣的話只需要在 \(x\) 兩個兒子合並的時候,要求不能同時存在 \(\geq a_i\) 的兩個星星。
所以可以寫一個 \(dp\) 出來,\(dp_{i,j}\) 表示 \(i\) 子樹代表區間中,最大高度為 \(j\) 的最大權值。
同樣考慮 \(j\) 的狀態數一定是子樹中的,同樣用線段樹合並優化一下轉移就行了。
有這樣一個更加簡單的做法:
將樓房,星星都按照縱坐標排序。
通過並查集維護聯通性,即當前情況下每個點向左向右不跨牆能到達的區間。
維護這樣一個信息 \(cost_i\),表示在 \(i\) 這個橫坐標放星星需要付出的代價。
考慮每個星星,如果 \(w_i \leq cost_x\),顯然不放這個星星更優。
否則暫時地選擇付出 \(cost_x\) 代價,放下這個星星,並通過並查集找到連通區間,區間加上 \(w_i - cost_x\),表示撤銷這次操作。
因為區間是不斷擴大的,大概可以理解這樣的做法是正確的。

loj 558 我們的 CPU 遭到攻擊
\(lct\) 來支持這些操作,為了方便進行邊化點操作,邊權轉化為點權。
因為要支持動態的\(link-cut\)操作,對每個節點維護這樣的一些信息:
\(lans\) 表示 splay 子樹內及其虛子樹內所有黑點到達當前根節點(及深度最小點)的答案。
\(rans\)\(lans\) 類似,但表示到達實鏈深度最大點的答案,其用處是在進行換根操作(下傳翻轉標記)時直接執行 swap(lans[x],rans[x])
\(val\) 表示當前點的點權,\(vals\) 表示 splay 子樹內邊權。
\(col\) 表示當前點的顏色,\(cnt\) 表示子樹內黑點個數的和。
\(ians\) 表示虛子樹內對答案的貢獻,\(icnt\) 表示虛子樹內黑點個數。
在執行 update(x) 的時候,實際上只要考慮跨過 \(x\) 並到達根節點的答案。
而且其中在右兒子部分的點權已經統計過了,只需要統計當前節點以及其左兒子的點權和。
那么有 \(lans[p]=lans[lch]+lans[rch]+ians[x]+(vals[lch]+val[x])*(cnt[rch]+icnt[x]+col[x])\)
在進行各種修改操作之前,首先將修改的點換成根節點,來保證祖先鏈處的值是正確的。

loj 6022 重組病毒
對於不含換根的操作,只需要處理一個 \(dfs\) 序。
在實際的 access 的虛實兒子改變的過程中用一個樹狀數組/線段樹執行區間加法操作。
對於詢問只需要區間查詢。
對於換根操作,可以發現經過分類討論,操作的 dfs 序仍可以表示為不超過兩段區間。
然而有一個很難辦的問題,換根過程可能存在影響。
仔細分析一下,實際上只要正常的進行換根,然后設置新根就行了。
因為在換根之后執行了一遍 access 操作,此時二者路徑上的虛邊均被打通。
也就是說二者路徑上不存在能產生貢獻的邊,而對於非二者路徑上的點,其在 dfs 序上其實不受這次換根的影響。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM