- 容易被忽略的東西
- 差分約束
- 矩陣快速冪
- 帶刪除的線性基
- 排序
- 定期重構
- 概率/期望DP
- 分治
- 歐拉phi函數
- 逆向思維
- 一類全序問題
- 一類貪心問題
- 莫隊
- 一類單點修改區間求和的問題
- 和排列有關的問題
- 用trie實現全部數$+1$,查詢全部數的異或和
- 莫比烏斯反演
- 分治FFT
- 組合數學
- 樹上的連通塊
- 輕重鏈剖分&長鏈剖分
- 高維前綴和 & 莫比烏斯反演
- 網絡流
- 點分樹
- 最小樹形圖
- 容斥方法 總結
- 和樹上兩點間路徑長度有關的技術&其他問題
- ZKW 費用流中處理掉負權的方法
- 用 dijkstra 代替 bellman-ford 跑費用流
- 單位根反演(求和引理)
- prufer 序列
- 樹形DP
- 用全局平衡二叉樹優化鏈剖+NTT
- DP優化
- 區間加&區間 $\gcd$
- 數位DP
容易被忽略的東西
分塊
二分答案
打表
差分
線段樹優化DP
差分約束
如果只有 \(a_i\leq a_j+d\) 的約束,就可以直接上差分約束。
如果有 \(a_i+a_j\leq d\) 的約束,考慮整張圖黑白染色,使得同色點之間只有差的約束,異色點之間只有和的約束,然后把白色的點的值取反,就可以跑差分約束了。
矩陣快速冪
觀察一下矩陣是否是循環矩陣,如果是就可以用FFT解決(循環卷積)。
用BM算法優化矩陣快速冪DP
記 \(B\) 為轉移矩陣。
那么 \(B\) 中每個元素都對應着同一個常系數線性遞推關系。
證明:
記 \(B\) 的特征多項式為 \(f(x)=a_0x^k+a_1x^{k-1}+\cdots+a_kx^0\),那么就有
這樣就可以求出前面 \(O(k)\) 項然后 \(O(k^2)\) BM一下得到遞推式再用倍增取模在 \(O(k^2\log n)\) 或 \(O(k\log k\log n)\) 內求出任意項的值了。
如果要求整個矩陣的遞推式的話,要拿這個矩陣哈希得到的值去BM,因為單項的最短遞推式可能不是整個矩陣的最短遞推式。
矩陣快速冪+DFT
DP轉移如下:
\(i\leq n,j\leq l,k\leq m\)
其中\(v\)只與\(j\)有關,最后求\(k=s\)或\(k\bmod m=s\)的值的和。
暴力搞的時間復雜度是\(O(l^3m^3\log n)\)的。
我們可以把這個東西看成一個多項式。
轉移就可以看成乘以一個多項式(單項式)。
如果求的是\(k\mod m=s\)的值的和,就可以看成循環卷積。
可以先求值,把每個點值拿去跑一遍矩陣快速冪,再插值回來。
時間復雜度:\(O(ml^3\log n)+\)點值插值的時間復雜度\(O(m^2)/O(m\log m)\)
多組詢問的矩陣快速冪優化DP
設矩陣大小為\(m\),次數是\(n\),詢問組數是\(t\),朴素的實現是\(O(tm^3\log n)\)的。
可以先把轉移矩陣的\(i\)次冪求出來。
每次詢問只需要拿一個\(1\times m\)的矩陣去乘轉移矩陣就行了。每次乘法是\(O(m^2)\)的。
時間復雜度:\(O(m^3\log n+tm^2\log n)\)
帶刪除的線性基
對於線性基中的每個向量和所有 \(0\) 向量維護這個向量是由哪些向量異或得到的。
在刪除一個向量 \(x\) 時,找到一個包含 \(x\) 的 \(0\) 向量,如果沒有就找線性基里位最低的包含 \(x\) 的向量,把這個向量的信息異或到其他包含 \(x\) 的向量的信息中即可。這樣在刪除時不會影響線性基中更高位的向量。
在向量個數比較小或強制在線是比較有用。
排序
有些題如果把權值(或者其他東西)從小到大排序按順序做,會有出人意料的效果。
定期重構
就每做 \(O(\sqrt q)\) 個修改就重構一下,每次詢問在建好的數據結構上查詢,還要把剩下的 \(O(\sqrt q)\) 的修改的影響一起算進去。
概率/期望DP
有一些概率/期望DP可以快速地推出這樣的式子:
BZOJ4872
XSY2472
分治
有一些問題求得是只包含/不包含一個點的情況,只需要考慮當前\([l,r]\)對\([l,mid]\)和\([mid+1,r]\)的影響。
下面來講一道例題
\(A(x)\)為\(n-1\)次多項式,\(B_i(x)\)為一次多項式,\(\forall i\)求\(A(x)\mod B_i(x)\)
直接做是\(O(n^2)\)的。
因為\((A(x)\mod C(x))\mod B_i(x)=A(x)\mod B_i(x)\)(\(C(x)\mod B_i(x)=0\))
設當前已經求出了
那么
所以我們可以遞歸下去做,直到求出所有的\(D_{i,i}\)
時間復雜度:
多點求值
XSY2469
歐拉phi函數
就是\(\varphi\)函數
誰都知道這個東西是個積性函數。
那如果\((a,b)\neq 1\)呢?
設\(d=(a,b)\)
可以發現,對於后面那部分
如果\(p\)只在\(a\)或\(b\)中出現過,那么只會在\(ab\)中出現。如果同時在\(a\)和\(b\)中出現過,那么會同時在\(ab\)和\(d\)中出現。
所以有
逆向思維
情況一
有時候我們做某個操作很不好做,我們可以先把所有操作做完后在一個個回復。
例如:給以一個圖,有兩種操作:1.刪邊;2.詢問連通性。
我們可以先把需要刪的邊刪掉,再一個個加回來,用並查集維護連通性。
情況二
有時候問你\(\forall A\),滿足要求的\(B\)的和。
我們可以枚舉所有的\(B\),計算每個\(B\)對每個\(A\)的貢獻。
AGC005F
一類全序問題
有\(n\)個物品,你要依次選擇這些物品,每個物品有三個屬性\(a_i,b_i,c_i\),當你選擇一個物品后,設當前選擇的物品的\(c\)屬性的和為\(s\),那么選擇這個物品的收益是\(a_i+b_is\),問你最大收益是多少。
假設我們已經欽定了一個順序。考慮兩個相鄰的物品(不妨設為前兩個),什么時候當前順序比交換后更優:
這樣我們就得到了一個全序關系。
那么能不能擴展到任意兩個物品的情況呢?
好像並不太行。我們需要換一種思路。
假設我們找到了一種最優解,但並不滿足以上的性質,那么一定可以交換相鄰兩個物品使得答案最優。所以直接排序貪心可以得到最優解。
如果題目還有其他限制,你也可以在得到這個順序后DP或者干其他事情。
一類貪心問題
有 \(n\) 個怪,組成了一棵樹。
打一個怪會先扣 \(a_i\) 滴血,在加 \(b_i\) 滴血。
怪的父親要在這個怪之前打。
問你初始時最少要有多少血才能把這個怪打完。
先不考慮父親比兒子早選的限制,把所有怪排序。
考慮第一個怪,如果可以直接打,就直接打完。
否則這個怪會在打完父親之后立刻打死,就把這個點和父親合並。
如果要對每個子樹求答案的話,可以用平衡樹維護操作序列,自底向上合並所有點的操作序列。
莫隊
總所周知,莫隊的時間復雜度和塊大小有關。
如果塊大小為\(t\),時間復雜度為\(O(\frac{n^2}{t}+mt)\)
如果塊大小為\(\sqrt n\),時間復雜度為\(O((n+m)\sqrt n)\)
如果塊大小為\(\frac{n}{\sqrt{m}}\),時間復雜度為\(O(n\sqrt m)\)
所以有時候可以通過調整塊大小來加速。俗稱調參。
一類單點修改區間求和的問題
有時候我們要修改一個點,求區間和。
算法 | 修改 | 求和 |
---|---|---|
樹狀數組/線段樹 | \(O(\log n)\) | \(O(\log n)\) |
分塊1 | \(O(1)\) | \(O(\sqrt n)\) |
分塊2 | \(O(\sqrt n)\) | \(O(1)\) |
樹狀數組/線段樹的做法很經典,這里就不講了。
分塊1:每次修改把對應的位置和對應的塊的和\(+1\)
分塊2:每次修改把對應的位置和對應的塊的和\(+1\),然后求出塊內前綴和、塊內后綴和、塊的前綴和。
有的人就要問了,分塊做法那么慢,有什么用呢?
用處大着呢!當修改次數與詢問次數不平衡的時候,我們可以做到比樹狀數組更優。
博主曾經用莫隊+分塊1水過了一道\(n=m={10}^6\)的題。跑的比zjt神犇的線段樹合並還快。
和排列有關的問題
很多問題讓你求對於每一個排列\(A\),如果\(\cdots\),那么\(\cdots\)。
我們可以考慮從小到大插入這\(n\)個數,插入第\(i\)個數時考慮這個數的貢獻。
用trie實現全部數\(+1\),查詢全部數的異或和
我們從低位到高位建一棵trie樹。
從根開始,交換左右子樹,然后對\(0\)的那棵子樹執行同樣的操作(進位)。
莫比烏斯反演
有很多題推着推着就推到\(\varphi\)上面去了。
說明可以用一條式子:\(\sum_{d|n}\varphi(d)=n\)
莫比烏斯反演的多組詢問
看起來沒辦法化簡了
這時候要枚舉\(j=id\)
設\(f(x)=\sum_{i|x}\mu(i)c^\frac{x}{i}\)
這樣\(f(x)\)就可以預處理出來了。
一般情況
這樣可以預處理后面的\(g(n)=\sum_{i|n}\mu(\frac{n}{i})f(i)\)
每次枚舉前面詢問。
時間復雜度:\(O(n+T\sqrt{n})\)
分治FFT
分治FFT一般有兩個用途。
求很多個多項式的乘積(普通分治)
設有\(n\)個多項式,次數之和是\(m\),那么時間復雜度就是\(O(m\log m\log n)\)。一共有\(\log n\)層,每層是\(O(m\log m)\)的。
求一類數列(CDQ分治)
數列\(f_n=\sum_{i=0}^{n-1}f_ig_{n-i}\)。對於一個分治區間\([l,r]\),先求出\([l,mid]\)的答案,再計算這部分對右邊\([mid+1,r]\)的貢獻。
時間復雜度:\(O(n\log^2 n)\)
組合數學
可以用插板法理解。
樹上的連通塊
樹上連通塊個數\(=\)點數\(-\)邊數。
點數/邊數中一般有一個是固定的。
輕重鏈剖分&長鏈剖分
輕重鏈剖分
每個重鏈頂端的子樹大小總和是 \(O(n\log n)\) 的。
每個點到根經過的輕邊個數是 \(O(\log n)\) 的。
適用於維護與樹的大小有關的信息。
長鏈剖分
每個長鏈頂端的子樹深度總和是 \(O(n)\) 的。
適用於維護與樹的深度有關的信息。
高維前綴和 & 莫比烏斯反演
復雜度為 \(O(\sum_{i}\frac{n}{p_i})=O(n\log log n)\)
for(int i=1;i<=cnt;i++)
for(int j=1;j*pri[i]<=n;j++)
f[j*pri[i]]+=f[j];
for(int i=1;i<=cnt;i++)
for(int j=n/pri[i];j>=0;j--)
f[j]+=f[j*pri[i]];
for(int i=1;i<=cnt;i++)
for(int j=n/pri[i];j>=0;j--)
f[j*pri[i]]-=f[j];
for(int i=1;i<=cnt;i++)
for(int j=1;j*pri[i]<=n;j++)
f[j]-=f[j*pri[i]];
網絡流
如果要的是最大收益,那么可以先強制選所有正的邊,然后把不選一條邊看成割掉這條邊,答案為正的邊權和 \(-\) 最小割。
流量平衡
可以先對於每條邊欽定一個方向,然后如果一條邊有流量就表示這條邊改了方向。對於一個點 \(i\),要根據出度入度的關系向源/匯連邊。
網格圖
把每一行看做一個點,把每一列看做一個點,選一個格子就在對應的行列之間連邊。
這是一個分層圖,用 dinic 跑會比較快。
一種限制
每個物品對應一條鏈,割一條邊代表這個物品對應的數/放在什么位置。
\(i\) 對應的鏈割了點 \(x\) 后面的邊時 \(j\) 對應的鏈必須割點 \(y\) 后面的邊
解決方法為連邊 \((x,y,\infty)\)。
有時候限制是 \(i\) 割了點 \(x\) 后面的邊時 \(j\) 必須割點 \(y\) 前面的邊
那么就要把一邊的鏈反過來。
常見的方法有:
橫着的鏈不變,豎着的鏈反過來。
黑白染色,白色的點對應的鏈不變,黑色的點對應的鏈反過來。
要注意前面的點屬於 T 集且后面的點屬於 S 集的情況。
要從后面的點往前面的點連容量為 \(\infty\) 的邊。
原圖中不能有 \(S\to T\to S\) 的路徑
把所有反向邊的容量設為 \(\infty\) 即可。
點分樹
二叉樹的點分樹中每個節點只有至多 \(3\) 個兒子。這樣可以直接可持久化這棵點分樹,可以減少一個 \(\log\)(把點分樹當成三叉數可持久化,深度是 \(\log\) 的)。
如果題目給的樹不是二叉樹,可以強行轉成二叉樹。
最小樹形圖
用可合並堆維護每個點的最小入邊,可以做到 \(O(n+m)\log m\)
容斥方法 總結
每次在剩下的物品中選一種拿走一個,求拿走的最后一個物品是第一種物品的概率 的問題
考慮容斥,枚舉哪些物品是在第一種物品拿完之后拿走的(剩下的隨意)。
那么剩下的物品就可以忽略了,只需要求出第一種物品是第一個拿完的概率。
對於 【UNR #3】百鴿籠 這道題,可以DP
考慮拿走第一種物品時每種物品拿了幾個,就可以DP了:
設 \(f_{i,j,k}\) 表示考慮完了前 \(i\) 種物品,有 \(j\) 種要在第一種取完之后才取完,已經取了 \(k\) 個物品。
最終長度為 \(l\) ,取了 \(j\) 種的序列的序列的貢獻是 方案數 \(\times {(-1)}^j\times {(\frac{1}{j+1})}^l\)
對於 【PKUWC2018】獵人殺 這道題,第一種物品是第一個取完的概率是 \(\frac{w_1}{\sum w_i}\)。可以用分治 NTT 計算方案數。
有 \(m\) 種顏色的球排成一行,共 \(n\) 個,求最終有 \(k\) 個同色的球相鄰的方案數
先假設每種球分成幾段,然后用分治 FFT 算出方案數。
但是這樣可能會有相鄰且同色的段
這時候就可以容斥了。
考慮每種方案把相鄰且同色的段合並之后有幾段
那么就有
https://www.cnblogs.com/ywwyww/p/8513349.html
有 \(m\) 種顏色的球排成一行,共 \(n\) 個,最終貢獻和同色段長度有關的
先算出每種長度的貢獻
假設想分成 \(i\) 段,但最終分成了 \(j\) 段,那么此時的容斥系數是 \({(-1)}^{i-j}\),方案數是 \(\binom{i-1}{j-1}\)
然后拿最終分成的段數去跑 DP 就好了。
https://www.cnblogs.com/ywwyww/p/9279670.html
和樹上兩點間路徑長度有關的技術&其他問題
比如說樹上長度不超過 \(k\) 的路徑數量 等等
現在主要有三種方法:
點分治/點分治樹
平衡樹合並/線段樹合並
長鏈剖分
這里講幾道題吧
問題\算法 | 點分治 | 平衡樹合並 | 長鏈剖分 |
---|---|---|---|
樹上長度不超過 \(k\) 的路徑條數(無邊權) | \(O(n\log n)\) | \(O(n\log n)\) | \(O(n)\) |
LOJ571 | \(O(n\log^2n)\) | \(O(n\log^2n)\) | \(O(n\log n)\) |
[WC2010]重建計划 | \(O(n\log n\log V)\) | \(O(n\log n\log V)\) | \(O(n\log V)\) |
ZKW 費用流中處理掉負權的方法
先用 SPFA 跑一邊最短路,處理出 \(S\) 到每個點的距離 \(d_i\)
顯然對於每一條邊 \((u,v,w)\) 都有 \(d_u+w\geq d_v\)
對於一條邊 \((u,v,w)\),把這條邊的邊權變為 \(w'=w+d_u-d_v\)。這樣整個圖中就沒有負權了。
容易證明在新圖上跑出的最短路就是原圖的最短路,只是長度有一點變化:\(d'_T=0\)
所以后面原點到匯點的距離要加上 \(d_T\)
用 dijkstra 代替 bellman-ford 跑費用流
還是先處理出 \(S\) 到每個點的距離 \(d_i\)。
還是把每條邊的邊權變為 \(w'=w+d_u-d_v\)。
因為新建的反向邊一定滿足 \(d_u+w_{u,v}=d_v\),所以 \(w_{v,u}'=-w_{u,v}+d_v-d_u=0\)
新圖的最短路還是比原圖的最短路少了 \(d_T\)
單位根反演(求和引理)
在碰到
時可以用求和引理優化:
prufer 序列
\(K_{n,m}\) 的生成樹個數是 \(n^{m-1}m^{n-1}\)
\(K_{n_1,n_2,\ldots,n_k}\) 的生成樹個數是 \(n^{k-2}\prod_{i=1}^k{(n-n_i)}^{n_i-1}\),其中 \(n=\sum_{i=1}^kn_i\)
有一個 \(n\) 個點,\(m\) 個連通塊,每個連通塊大小為 \(a_i\) 的森林。你要加上若干條邊,讓這個森林變成一棵樹。方案數為
樹形DP
\(f_{i,j}\) 為以 \(i\) 為根的子樹,選出來的點數為 \(j\) 時的方案數/貢獻。這里 \(j\leq k\)。
轉移時要枚舉兩邊各選了多少點。直接做是 \(O(nk^2)\) 的。
注意到當選的點數 \(\leq size\) 時才有意義。這樣轉移時兩棵子樹選的點數可以只枚舉到 \(\min(size,k)\),這樣就是 \(O(nk)\) 的了。
證明:
1.兩棵子樹大小都 \(>k\)。只有 \(O(\frac{n}{k})\) 次轉移,復雜度為 \(O(\frac{n}{k}\times k^2)=O(nk)\)。
2.一棵子樹大小 \(\leq k\),另一顆子樹大小 \(>k\)。對於所有的這類轉移,小的那棵子樹的大小之和是 \(O(n)\) 的。復雜度為 \(O(n\times k)=O(nk)\)。
3.兩棵子樹大小都 \(\leq k\)。把所有這類轉移在樹上標出來,那么會標出很多棵子樹。每棵子樹復雜度為 \(O({size}^2)\),其中 \(size\leq 2k\)。復雜度為 \(O(nk)\)。
這樣總的復雜度就是 \(O(nk)\) 了。
用全局平衡二叉樹優化鏈剖+NTT
如果 \(f_x\) 的次數 \(x\) 子樹的深度有關,就可以直接長鏈剖分+分治NTT做到 \(O(n\log^2n)\)。
否則就要用重鏈剖分+分治NTT。復雜度為 \(O(n\log^3n)\)。
但是我們可以在全局平衡二叉樹上面合並。
一個點 \(x\) 有 \(size_{ls}\leq \frac{1}{2}size_x,size_{rs}\leq \frac{1}{2}size_x,\max(size_v)\leq \frac{1}{2}size_x\)(這里 \(v\) 是 \(x\) 的輕兒子(虛兒子))。
這樣只會跳 \(O(\log n)\) 次實邊。
那虛邊呢?
如果是和子樹深度有關的題,直接把 \(f_v\) 加起來就好了。總復雜度為 \(O(n\log^2n)\)
如果是和子樹大小有關的題,可以再用一次全局平衡二叉樹的思想:找到一個點 \(v\) 滿足 \(size_{ls}\leq \frac{1}{2}\sum size_v,size_{rs}\leq \frac{1}{2}\sum size_v\),但是中間那個兒子的 \(size_y\) 可能會 \(>\frac{1}{2}\sum size_v\)。但這並不影響復雜度,因為 \(size_y<\frac{1}{2}size_x\),所以從一個點的某個虛兒子 \(v\) 跳到 \(x\) 需要的步數是 \(O(\log\frac{\sum size_v}{size _v}+1)=O(\log \frac{size_x}{size_v})\)。所以總的復雜度就是 \(O(n\log^2n)\)。
DP優化
記 \([l_i,r_i]\) 為 \(i\in g(k-1)\) 可以轉移到的 \(g(k)\) 中的區間(或者能轉移到 \(g(k)\) 的區間)。要求任意兩個 \([l_i,r_i],[l_j,r_j]\) 不互相嚴格包含。(這里嚴格包含指的是包含且左端點不同且右端點不同)
我們可以把 \(g(k)\) 切成若干個區間,滿足 \([l_i,r_i]\) 不被任意一個區間嚴格包含,且 \([l_i,r_i]\) 最多與兩個區間相交。
方法如下:對於一個區間的左端點,找到最右的右端點使得這個區間不嚴格包含任何 \([l_i,r_i]\)。可以證明,這個方法滿足條件。
這樣,一個 \([l_i,r_i]\) 一定是一個區間的前綴或后綴,分兩部分DP一下就好了。DP過程類似決策單調性優化DP。
區間加&區間 \(\gcd\)
維護差分后的序列即可。
數位DP
有時候會遇到很多個數的和 \(\leq m\),每個數 \(\leq n\),還有一些其他限制的計數題。
可以從低位往高位數位DP,記錄進位和只考慮低位的大小關系。