圖片排版
題目來源
題目大意
有\(n\)張圖片,第\(i\)張圖片大小為\(A_i \times B_i\)。
要把這些圖片按順序放到一個寬度為\(m\)的文檔里,原則如下:
- 如果這一行剩余寬度為0,則跳至下一行;
- 如果這一行剩余寬度大於等於該圖片的寬度,則放置;
- 否則把該圖片長寬等比縮小,使得寬度剛好可以放下,高度向上取整;
- 整個文檔會排列成若干行,每一行的高度為最高的圖片的高度。
你可以刪去其中某一張圖片,使得剩余圖片按原順序重新排版,總高度最小。
范圍:\(n \le 100000\),\(m \le 100\)。
算法介紹
令\(F(i)\)表示前\(i\)張圖片放置的結果,需要記錄行數、剩余寬度、當前行高度。
令\(G(i,j)\)表示最后\(i\)張圖片,從剩余寬度\(j\)開始放置的結果,需要記錄行數、當前行高度。
刪除一張圖片,只需要對應的合並\(F\)和\(G\)即可。
可以用動態規划得到\(F,G\),效率為\(O(nm)\)。
震盪數組
題目來源
題目大意
對於一個長度為\(n\)的數組\(A\),若對於所有\(i \in (1, N)\)都滿足,\(A[i]> \max(A[i-1],A[i+1])\)或者\(A[i]< \min(A[i-1],A[i+1])\),則稱該數組為震盪數組。
現在給出數組\(A\),你可以對它進行若干次操作,每次操作可以交換一對數。請問最少進行多少次操作,可以把\(A\)數組變成震盪數組。
范圍:\(n \le 29\),\(A_i\)兩兩不同。
算法介紹
容易發現,答案的上界是\(\frac{n}{4}\);所以直接搜索就可以啦!
當然需要加入一些剪枝。
效率:\(O(\text{玄學})\)。
子矩陣求和
題目來源
題目大意
有一個無限大的矩陣\(A\),\(A_{i,j}= \min(i,j)\)。
給出\(n,m,P\),找出矩陣\(A\)中一個\(n \times m\)的子矩陣,滿足元素和是\(P\)的倍數。要求該矩陣的左上角坐標盡量小。
范圍:\(n,m \le 10^5\),\(P \le 10^6\)。
算法介紹
首先,若給出\(A\)中的一個子矩陣\((x0,y0,x1,y1)\),是可以\(O(1)\)計算和的;容斥一下即可。
觀察發現,有\(A_{i+1,j+1}= A_{i,j} + 1\),所以當一個\(n \times m\)的子矩陣整體向右下方移動\(t\)格時,元素和就增加了\(tnm \mod{p}\)。
所以只需要枚舉第一行(列)的位置\(x\),先算出左上角\((1,x)\)的矩陣和,然后用拓展歐幾里得就可以得到整體向下移動的格數。
容易發現,左上角為\((1,n)\)和\((1,n+1)\)以及\((1,n+2),...\)的矩陣和相同,所以不需要繼續枚舉\(x\)了。
因為本題\(P\)較小,可以先\(O(P)\)預處理,這就不要拓歐了。
效率為\(O((n+m) \log P)\)或\(O(n+m+P)\)。
神奇的子串
題目來源
題目大意
對於一個長度為\(n\)的01串\(S\),它的權值定義為,它所有長度在\([n-K+1,n]\)之間的子串中,本質不同的子串個數。
給出\(n,K\),要求對所有可能的串\(S\)(一共\(2^n\)種),計算權值和。
范圍:\(n \le 10^8\),\(K \le 15\)。
算法介紹
如果想讓多個相同的項只貢獻1,通常的方法有:
- 最小表示法(或稱“唯一表示法”),即讓多個相同項只在某個代表元素上被計算到一次。
- 容斥,比如二項式反演。
本題可以用容斥來解決。
首先枚舉\(t \in [1, K]\),則需要計算串\(S\)的\(t\)個子串中本質不同的數量。可以\(2^t\)枚舉哪些串是相同的,接着用並查集得到哪些位置的元素是相同,若最后串\(S\)有\(x\)個不同的集合,則貢獻為\(2^x\)。
串長很大,朴素的並查集合並效率爆炸。容易發現,串\(S\)是呈類似“循環串”的形式,故只需要保留最后\(2t\)個位置即可。
效率為\(O(2^K K^3)\)。
Rikka with Tree III
題目來源
題目大意
給一棵\(n\)個節點的樹,每個點有點權\(w_i\)。
若三元組\((x,y,z)\),點\(y\)在點\(x\)與點\(z\)的最短路徑上,\(w_x,w_y,w_z\)呈等差數列,則稱\(w_y-w_x\)(即公差)是合法的。
求有多少種不同的合法公差。
范圍:\(1 \le w_i \le n \le 10^5\)。
算法介紹
算法一
點\(y\)為點\(x\)和點\(z\)的LCA
這是一個經典的問題,直接啟發式合並即可,效率\(O(n \log^2 n)\)。
點\(x\)在點\(y\)的子樹內,點\(z\)在點\(y\)的子樹外
顯然只需要得到點\(y\)子樹內所有點權值的bitset,以及子樹外所有點權值的bitset,取交即可。
然而因為內存的限制,無法用DFS得到bitset,這讓我們很苦惱!
可以用DFS序,把子樹內和子樹外轉化為了區間信息;然后用莫隊就可以了!
效率為\(O(n \sqrt{n} + \frac{n^2}{32})\)。
算法二
將樹輕重鏈剖分后,每個點向上最多會跳\(O(\log n)\)條不同的重鏈;這也意味着,如果對於每個點,都將它所有輕兒子的子樹DFS都遍歷一遍,總效率為\(O(n \log n)\)。利用這個性質,我們來分別優化算法一中的兩個計數部分。
點\(y\)為點\(x\)和點\(z\)的LCA
首先我們得到了點\(y\)子樹內點的權值信息(每種權值有幾個,不止是bitset)。先把所有輕兒子子樹都刪去,然后再逐一將輕兒子子樹加入,加入時順便得到合法公差。
得到點\(y\)重兒子的子樹權值信息,只需要將輕兒子刪光即可;得到輕兒子子樹權值信息,只需要暴力DFS加入即可。
效率為\(O(n \log n)\)。
點\(x\)在點\(y\)的子樹內,點\(z\)在點\(y\)的子樹外
首先我們得到了點\(y\)子樹內的權值信息,以及子樹外的權值信息;同時還知道全局的權值信息。
重兒子 只需要暴力將所有輕兒子子樹內的點轉移,就能得到重兒子子樹內和子樹外的信息。
輕兒子 直接在全局的權值信息中,把輕兒子子樹的點轉移,就能得到輕兒子子樹內和子樹外的信息。
注意,我們需要對每條重鏈分別做。鏈頭(必然是父親的輕兒子)的子樹信息直接從全局處得到,之后只DFS重兒子;重兒子信息由刪去輕兒子子樹得到。每條重鏈做完后需要還原全局信息
因為本題的bitset必不可少,所以不太能顯示出算法二的強勢......
時間復雜度\(O(n \log n + \frac{n^2}{32})\),空間復雜度\(O(n)\)。
Rikka with String
題目來源
題目大意
給一個長度為\(n\)的字符串\(S\),需要對於每個\(i(0<i<n)\)求出:假設將串\(S\)在第\(i\)位和第\(i+1\)位間裂開成兩個串\(S_1\)和\(S_2\),有多少不同的串是\(S_1\)或\(S_2\)的子串。
范圍:\(n \le 200000\)。
算法介紹
處理字符串的子串問題,通常采用SAM。SAM中的每個節點\(p\)就表示了以\(right_p\)集合為右端點,長度在\((Max_{fa_p},Max_p]\)之間的一坨子串;每個節點表示的子串都是互不相同的。
通常並集比較難求,於是考慮補集轉化后求解交集。即用 (串\(S\)中的子串個數 - 既不是\(S_1\)也不是\(S_2\)的子串個數) 得到答案。
假設某個子串在串\(S\)中的出現位置為\([l_1,r_1],[l_2, r_2],...,[l_k,r_k]\),考慮該串會對哪些\(i\)產生貢獻(即沒有在\(S_1\)和\(S_2\)中出現),只需要滿足\(i\)選在\([l_1,r_1],[l_2, r_2],...,[l_k,r_k]\)的交上即可,即對所有\(i \in [\max(l_1,l_2,..,l_k), \min(r_1,r_2,...,r_k)]\)產生貢獻。
對於串\(S\)的SAM中每個節點\(p\)代表的串統一考慮,只需要特判幾種情況,對\(len \in (Max_{fa_p},Max_p]\)的交進行區間加一;可以轉化為區間加公差為1的等差數列。
效率為\(O(n)\)。
拓展
如果題目變成求“有多少不同的串是\(S_1\)和\(S_2\)的子串”,方法類似。
Rikka with Grid
題目來源
題目大意
給出一個\(n \times n\)的01矩陣。進行\(m\)次詢問,要求有多少大小為\(a \times b\)的,四條邊界全為1的子矩陣。
范圍:\(n,m \le 1500\)。
算法介紹
朴素的想法,令\(A[i][j][k]\)表示第\(i\)行,向下至少有\(j\)個連續全1的格子,第\(k\)列是否合法;\(B[i][j][k]\)表示第\(i\)行,向左至少有\(j\)個連續全1的格子,第\(k\)列是否合法。
\(A,B\)的最后一維可以用bitset表示,且\(A[i][j],B[i][j]\)可以由\(A[i][j-1],B[i][j-1]\)得到,效率為\(O(\frac{n^3}{32})\)。
每次詢問,先枚舉上邊界是\(i\)行,通過\(A[i][b],A[i+a-1][b],B[i][a]\)的bitset移位取交,就可以計算答案啦!時間和空間復雜度均為\(O(\frac{n^3}{32})\)。
可以用st表優化內存。以\(A\)為例,預處理時得到\(A[i][2^0],A[i][2^1],...,A[i][2^k]\)的信息;對於任意的\(j\),\(A[i][j]\)可以由\(A[i][2^t]\)和\(A[i+j-2^t][2^t]\)取交得到。空間復雜度為\(O(\frac{n^2 \log n}{32})\)。
Little Y's Tree
題目來源
題目大意
給一棵\(n\)個節點的樹,進行\(q\)次詢問。
每次詢問給出\(K\)條邊,假設這\(K\)條邊從樹上刪除,整棵樹會划分成\(K+1\)個連通塊,求出每個連通塊的直徑的和。
范圍:\(n,q,\sum{K} \le 10^5\)。
算法介紹
求樹上一個點集的直徑
這是一個經典問題。考慮逐個加入點\(x\),時時維護出點集直徑的兩個端點\((a,b)\);拿\((a,x)\)和\((b,x)\)去更新直徑即可。
如果合並兩個點集,只需要拿兩個點集各自直徑的兩個端點(一共4個點),兩兩組合更新直徑即可。
連通塊 -> DFS序
容易發現,刪去\(K\)條邊后,剩下的\(K+1\)個連通塊都可以表示成一些DFS序的並。所以用線段樹維護每個DFS序區間的直徑即可。
效率為\(O((n+q+\sum{K}) \log n)\)。
Automorphism
題目來源
題目大意
對於兩棵\(n\)個節點的無根樹\(T_1,T_2\),它們同構當且僅當,存在一個排列P使得\(T_1\)中\((u,v)\)有邊當且僅當\(T_2\)中\((P(u),P(v))\)有邊。
一棵樹\(T\)的自同構數量為\(T\)中\((u,v)\)有邊當且僅當\(T\)中\((P(u),P(v))\)有邊的排列\(P\)的數量。
請問有多少棵本質不同的(不同構),自同構個數不超過\(m\)的無根樹。
范圍:\(n \le 50\),\(m \le 10^9\)。
算法介紹
樹的自同構計數
有根樹
令\(F[u]\)表示點\(u\)子樹的自同構排列數量,假設子樹中有\(x\)個同構的子樹,它們的自同構數都是\(y\),則貢獻為\(y^x x!\);而\(F[u]\)就等於所有貢獻相乘。
無根樹
取重心為根,轉化為有根樹情況。遇到雙重心情況,若重心兩側的子樹同構,則答案再乘二。
本質不同的樹計數
有根樹
令\(A_i\)表示\(i\)個點本質不同樹的數量,\(B_i\)表示\(i\)個點本質不同的森林數量。
\(A_i= B_{i-1}\),再把\(A_i\)扔進去和\(B\)背包一下更新\(B\)。
無根樹
默認根是重心,則只允許將\(i \le \frac{n}{2}\)的\(A_i\)拿進去背包。
對於雙重心的情況,如果重心兩側的子樹不同構,則同種樹會被重復算到2次,需要減去。
原問題
原問題是兩者的結合,考慮用Dp套Dp。
先預處理一個系數\(c[i][j][k]\)表示有\(i\)個本質不同的樹(但是每種樹可以選多個),一共選了\(j\)棵樹,\(k\)為自同構數量的一部分(若某個樹重復選了\(x\)個,則貢獻為\(x!\),\(k\)為貢獻的乘積;只保留不超過\(m\)的\(k\),顯然狀態數很少),\(c[i][j][k]\)表示這種情況下的方案數。
令\(A_{i,j},B_{i,j}\)分別表示\(i\)個點,自同構數為\(j\)的樹和森林的數量(只保留不超過\(m\)的\(j\),顯然狀態數很少)。同樣有\(A_{i} = B_{i-1}\),再把\(A_i\)扔進去和\(B\)背包一下更新\(B\)。
背包時,需要對於所有\(j\)枚舉\(A_{i,j}\);然后枚舉從\(A_{i,j}\)選了\(a\)個本質不同的樹(乘個組合數),一共選了\(b\)個樹(同一種樹可以選擇多個,貢獻乘個\(j^b\));然后對所有\(k\),枚舉\(c[a][b][k]\);再枚舉所有\(B_{x,y}\)來背包。
因為是無根樹,需要令根為重心,即只允許將\(i \le \frac{n}{2}\)的\(A_i\)拿進去背包。且最后還需要判斷雙重心的情況。
復雜度為\(O(\text{玄學})\),因為有效的Dp狀態數非常少,所以能過。
Rikka with Sequence II
題目來源
題目大意
給出\(n\)個數\(A_i\),要求選出若干個數,使得平均數小於等於中位數;求方案數。
范圍:\(n \le 40\)。
算法介紹
先將序列\(A\)從小到大排序,然后枚舉\(i,j\)確定中位數\(Mid= (A_i + A_j)(2)\)。將另外數都減去\(Mid\),對於\(t \in [1,i)\)的\(A_t\),權重為\(-1\);\(t \in (i, j)\),權重為\(0\);\(t \in (j, n]\),權重為1。我們就要求出剩下數中有多少子集,權重恰好為0,權值和小於等於\(Mid\)。
可以用折半搜索求解。值得注意的是,如果先\(DFS\)出所有集合然后排序,效率會多乘只\(n\)。考慮用BFS的形式拓展,考慮當前數是否選,將兩個數組歸並排序即可。
效率為\(O(2^{\frac{n}{2}} n)\)。
Rikka with Graph
題目來源
題目大意
給出一張\(n\)個點\(m\)條邊的無向圖。考慮這張圖所有邊的子集(一共\(2^m\)個),對於每一個邊子集,建出它的導出子圖,這個子圖由若干聯通塊構成,要求至多一個聯通塊是基環樹,其他都是樹。該方案的權值,為所有連通塊的大小的乘積。
對所有情況求權值和。
范圍:\(n \le 16\)。
算法介紹
算法一
先\(2^n\)枚舉環,將其縮點;再建立一個輔助點,將該點與圖中剩下點以及新縮點連邊,求生成樹個數即可。效率為\(O(2^n n^3)\)。
需要預處理每個點集\(S\)構成環的方案數,可以\(O(2^n n^2)\)Dp出來。求生成樹個數可以對基爾霍夫矩陣求行列式!
算法二
顯然只需要對於所有點集\(S\),求出\(S\)構成環的方案數、構成樹的方案數、構成基環樹的方案數;用個simple的子集Dp合並起來就行了。前兩者都非常好求,這里關注如何對於所有點集,求出基環樹的方案。
效仿容斥求拓撲圖個數的方法,令\(F(S)\)表示點集\(S\)的基環樹方案,枚舉該基環樹最外面的一層點(即葉子集合),將它“剝掉”后剩下的還是一棵基環樹。再用容斥的思想,枚舉最外層的點集\(T\),要求這些點一定是葉子,但是對於\(S-T\)中的點,也可能是葉子。即:
令\(Deg(x,B)\)表示點\(x\)在點集\(B\)中有多少條邊,則\(E(A,B)=\prod_{x \in A} Deg(x,B)\)。
稍加留心會發現,該算法的瓶頸在於計算\(E(A,B)\),暴力求解的效率為\(O(3^n n)\)。可以把枚舉子集轉化為枚舉超集,即枚舉集合\(S-T\),對所有點\(x\)預處理\(Deg(x,S-T)\);之后DFS所有可行\(T\),順便計算出\(E(T,S-T)\)即可。
效率為\(O(3^n)\)。
歐拉子圖
題目來源
題目大意
給一張\(n\)個點\(m\)條邊的無向圖。
考慮它所有邊集的子集(一共\(2^m\)個),對於每一個子集,它構成的圖必然是若干個連通塊,要求每一個連通塊都存在歐拉回路;這種方案的權值即為邊集大小的平方。
求所有方案的權值和。
范圍:\(n \le 60\),\(m \le 100\)。
算法介紹
方法一
如何判定一張圖的每個連通塊都存在歐拉回路? 顯然只需要每個點度數均為偶數即可。
如何單純計算方案數呢? 直接高斯消元解異或方程組!最后2的自由元個數次冪就是答案。用個bitset優化,效率為\(O(\frac{1}{32} n^2 m)\)。
現在要對平方求和,不妨把每個平方的貢獻分別對應到每一對邊上。即\(O(m^2)\)枚舉兩兩對邊,然后用高斯消元求出包含這兩條邊的歐拉圖方案數,累加即可。
效率:\(O(\frac{m^3 n^2}{32})\)。
方法二 —— K次方
該方法常用於稀疏圖上
考慮一個暴力Dp,令\(F[i][j][t]\)表示處理了前\(i\)條邊,\(j\)是一個\(2^n\)的狀態,記錄每個點的奇偶性,\(t\)的范圍為\(0..K\),表示\(t\)次方的答案。每次考慮第\(i+1\)條邊是否選,隨便轉移一下即可。效率為\(O(m 2^n K)\)。
事實上\(j\)的狀態數是遠遠不到\(2^n\)的。對於每個點\(x\),找出它在這\(m\)條邊中第一次出現的位置,以及最后一次出現的位置。顯然點\(x\)只有在這段區間中,狀態才可能是0或者1;否則狀態一定是0,根本沒必要記它。所以對於每個\(i\),\(j\)的狀態數為2的覆蓋點數次冪個。
因為是稀疏圖,通過隨機重置這\(m\)條的順序,可以在很快的時間內找出狀態數總和\(S\)小於等於\(10^6\)的方案。之后Dp的效率就為\(O(SK)\)。
String Problem III
題目來源
題目大意
給出\(n\)個字符串\(S_i\),求兩兩之間編輯距離在\([1,8]\)之間的對數。
編輯距離為:每次刪除、插入或修改一個字符,使得兩個串相等的最少操作步數。
范圍:\(n \le 200\),\(\sum |S_i| \le 1000000\)。
算法介紹
求解兩個串的編輯距離 似乎只能Dp吧?令\(F[i][j]\)表示\(A\)串前\(i\)個和\(B\)串的前\(j\)個的編輯距離;隨便轉移一下。效率為兩者串長的乘積。
這道題顯然不能用Dp算,太慢了。注意到一個神奇的限制,距離小於等於8,不妨用搜索。
初始設兩個指針\(x,y\)都為1,求出\(x,y\)之后串的LCP,得到第一個不同的位置。對於這個位置,要么在\(A\)串刪掉,要么在\(B\)串中刪除,要么進行替換,這樣就進行了一次操作。因為編輯距離不超過8,所以只要\(3^8\)枚舉前8次操作即可。
效率:\(O(n^2 3^8)\)。
MX Loves Bomb
題目來源
題目大意
給出一棵\(n\)個點的樹,你需要炸毀所有邊,每條邊只能被炸一次。轟炸有兩種類型:
- 選擇點對\((u,v)\),將點\(u\)到點\(v\)最短路上的所有邊炸毀。
- 選擇點\(u\),將點\(u\)的所有出邊炸毀。
如果需要轟炸的邊中有一條之前已經被轟炸,則不能進行該操作。
求最少幾次操作可以將所有邊都轟炸。
范圍:\(N \le 10^5\)。
算法介紹
簡化版本1 如果沒有第二種操作,且第一種操作可以重復轟炸邊,則答案為葉子數除以2向上取整。
簡化版本2 如果沒有第一種操作,因樹是一張二分圖,所以答案必然是把二分圖左邊的點全選或者把右邊的全選,取較小者。
簡化版本3 如果沒有第二種操作,則答案為度數為奇數的點數除以2。
證明 : 若一張連通圖能一筆畫,則要求度數為奇數的點個數是0個或者2個;因為樹是沒有環的,所以當所有點度數均為偶數時,轟炸就結束了。每次轟炸可以選擇一條一筆畫的路徑,這就消除了兩個奇數點。故答案就是度數為奇數的點數除以2。
原問題 直接樹形Dp搞它!
令\(F(u,0..1)\),表示以點\(u\)為根的子樹,有0或1條邊往父親伸展,將整個子樹轟炸的最少步數。隨便轉移一下即可。
備注
前三個簡化版本因為結論簡單,可以很輕松地套個限制,出成Dp套Dp。
高等理論計算機科學
題目來源
題目大意
給出一棵\(n\)個點的樹,再給出\(m\)條樹鏈。求有多少對樹鏈有交。
范圍:\(n,m \le 100000\)。
算法介紹
方法一
單純判斷兩條樹鏈是否有交,只需要判斷第一條鏈的LCA是否在第二條鏈上,或者第二條鏈的LCA是否在第一條鏈上即可。當兩條鏈的LCA相同時,這兩個條件都可以滿足;否則兩條樹鏈有交只可能滿足上述其中一個條件。
對於這\(m\)條鏈,在LCA處都加上1;之后對於每條鏈詢問鏈和即可;可以預處理好每個點到根路徑上的和,效率為\(O(n)\)。
方法二
容易發現,兩條樹鏈的交仍然是樹鏈;而每條樹鏈中,點數剛好是邊數多1。可以由此容斥,將樹鏈的交只貢獻1的代價。
具體來說,對於所有點和邊,都求出有多少條樹鏈覆蓋它;將點的答案減去邊的答案即可!效率同樣為\(O(n)\)。
交換代數
題目來源
題目大意
給一個長度為\(n\)的01序列\(A\),該序列共有\(\frac{n(n+1)}{2}\)個區間。進行若干次操作,每次操作會隨機選擇一個區間,然后將該區間內所有數取反。
求期望多少次操作后,序列\(A\)變成全0。
范圍:\(n \le 20\)。
算法介紹
期望
一般求解期望的題有這樣幾種思路:
- 期望線性性搞它!
- 高斯消元暴力解!
- 將期望轉化為對將所有時刻的概率相加,通常用容斥算概率。
很遺憾,本題似乎不能用以上方法裸算,需要轉化模型!
區間異或
對於區間操作,常見的思路就是將序列差分,隨之簡化為單點操作。
具體來說,我們新建序列\(d\),令\(d_i = A_i \bigoplus A_{i-1}\)。這樣區間操作就簡化為,等概率選擇兩個不同的\(i,j\),將\(d_i,d_j\)取反。
此時驚喜地發現,我們只需要知道全局有幾個1就可以輕松轉移了!因為最后的目標是將所有數都變成0,而每次操作中,每個位置被選擇的概率是一樣的。
令\(E(i)\)表示全局有\(i\)個1時,最終變成全0,期望走多少步。可以很輕松列出方程,直接高斯消元就可以了!
效率:\(O(n^3)\)。
時空陣
題目來源
題目大意
有一張\(n\)個點的無向圖,每條邊長度為1。
求有多少圖(一共\(2^{\frac{n(n-1)}{2}}\)種可能)滿足,\(1\)號點至\(n\)號點的最短路長度恰好為\(m\)。
范圍:\(n,m \le 100\)。
算法介紹
根據最短路的性質,可以考慮在最短路圖上Dp。
具體來說,令\(F[i][j][k]\)表示做到最短圖上的第\(i\)層,圖上一共有\(j\)個點,第\(i\)層上有\(k\)個點的方案數。枚舉第\(i+1\)層上有\(t\)個點,則這\(t\)個點至少要向第\(i\)層的點連一條邊;同時這\(t\)個點之間可以互相連邊。隨便乘個系數就可以轉移了。
效率為\(O(n^4)\)。
好排列
題目來源
題目大意
給出長度為\(n\)的序列\(A\),求有多少排列\(B\),使得對於所有\(i\)都滿足:
范圍:\(n \le 10^6\)。
算法介紹
這道題的模型相當於給定一張拓撲圖,要求合法的標號方案數;據我所知這個問題應該是不能做的。
事實上,根據序列\(A\),我們可以得到排列\(B\)的笛卡爾樹;換言之,所建立的拓撲圖是能夠簡化成樹形態的。
具體建樹過程為:
- 建立棧\(S\),棧中存儲的元素,隨着下標的增加,權值遞減;即有\(B_{S_i} > B_{S_{i+1}}\)。
- 從左到右加入每個元素\(A_i\),將棧中被\(A_i\)控制的元素踢出;踢出元素時順便建立笛卡爾樹。
建樹之后便是個經典問題,直接DFS遍歷樹,乘個組合數即可算出答案。復雜度為\(O(n)\)。
字體設計
題目來源
題目大意
給長度為\(n\)的序列\(A\),要求選出其中一些位置,使得所有未被選出的位置\(x\),令它左右兩邊最近的被選出的位置分別為\(l,r\)(這兩個位置必須存在),滿足\(A_l < A_x < A_r\)或者\(A_l > A_x > A_r\)。
序列\(A\)中每個數互不相同。
范圍:\(n \le 10^5\)。
算法介紹
算法一
首先\(A_1\)必須選擇,若\(A_1 < A_2\),則找出最靠左的\(x\),滿足\(A_x < A_1\),則\([2,x-1]\)均大於\(A_1\)。顯然\([2,x-1]\)中最大數必須選,接着再從這個數開始往右拓展。若\(A_1 > A_2\),情況類似。
找每個數往右第一個大於(小於)它的數,可以用經典的懸線法(是叫這個名吧)解決,效率\(O(n)\)。
RMQ可以用st表也可以四毛子,效率\(O(n \log n)\)或\(O(n)\)。
算法二
考慮遞歸解決問題,令\(Work(l,r)\)表示區間\([l,r]\)的答案。令\(x= min(l,..,r),y= max(l,..,r)\),則\(x,y\)之間的數全部滿足要求;只需要繼續遞歸\(Work(l,x),Work(y,r)\)即可(假設\(x<y\))。
需要支持RMQ操作,效率\(O(n \log n)\)或\(O(n)\)。
K個串
題目來源
題目大意
給出長度為\(n\)的序列\(A\),對\(A\)中一個區間\([l,r]\),它的權值定義為其中不同的數的和,即重復出現的數只算一次權值。
求第\(K\)大區間的權值。
范圍:\(n,K \le 100000\)。
算法介紹
關於第K優解
第\(K\)優解問題一般有以下三種思路:
- 二分答案 先二分答案,之后驗證。若\(K\)較小,則考慮暴搜出所有解,當解的個數超過\(K\)時,則停止;若\(K\)較大,則考慮快速計算出解的個數,一般采用數據結構維護。
- 直接在數據結構上查詢 比方說用權值線段樹(字母樹)在\(O(\log n)\)的時間復雜度找出第\(K\)小(大);用SAM在\(O(length)\)的時間復雜度內找出字典序第\(K\)小的子串……
- 堆 類似於A*算法,對堆中每個狀態都記下當前值和估價值,每次取出當前值加估價值最優的狀態進行拓展,一般用數據結構(可持久化堆,主席樹等)來維護所有的估價(或者說后繼)狀態。
簡化版本
若序列\(A\)中所有的元素都不同(即區間\([l,r]\)的權值為\(A_l+A_{l+1}+...+A_r\)),則這是一個經典問題,可見超級鋼琴。
有兩種思路:
- 二分答案,枚舉左端點,計算有多少右端點合法。這是一個二維數點模型,可以預處理好主席樹。復雜度為\(O(n \log^2 n)\)。
- 令堆中每一個狀態為\((l,a,b)\),再令\(r=RMQ(a,b)\)。從堆中取出\((l,a,b)\)后,再加入狀態\((l,a,r-1)\)和\((l,r+1,b)\)。用st表來詢問區間極值(RMQ),復雜度為\(O(k \log n)\)。
原問題
本題較超級鋼琴多了一個限制,即區間\([l,r]\)的計算方式更為復雜。不能在全局詢問\(r\),而需要對不同的\(l\)分別對待。
其實這也很好對付,對於每個\(l\)都建立主席樹,主席樹上對每個\(r\)都把區間\([l,r]\)的權值存儲下來。令\(l\)從\(n\)倒着枚舉到\(1\),先令\(l\)的主席樹從\(l+1\)處繼承。令\(next[l]\)表示\(min \{ x|A_l=A_x \}\),則\(l\)的加入,會使得所有\(r \in (l, next[l])\)的區間\([l,r]\),權值都增加上\(A_l\)。只需要對主席樹進行區間加操作即可。
之后的做法就和簡化版本類似了,復雜度為\(O((n+k) \log n)\)。