知識點梳理
Day1 6.7
可能是圖論專題吧
Tarjan求強連通分量
- dfn[]表示dfs序的標號
- low[]表示能連到的最小的dfn標號的點的標號
- sta[],top是棧
- instack[]標號是表示這個點在棧中,還是已經遍歷完被彈棧了
- col[]記錄每個點所在的顏色
1.Tarjan縮點后是一張拓撲圖
2.標號的倒序是這張拓撲圖一個合法的拓撲序
Tarjan求雙連通分量,割點與橋
- dfn[]表示dfs序的標號
- low[]表示能連到的最小的dfn標號的點的標號
- sta[],top建立一個棧
- 並沒有instack
1.是點雙而不是邊雙的只有兩點一線
2.邊雙縮點后是一棵樹
3.一個點可能再多個點雙中,於是我們求點雙的時候需要在兒子處進行統計low[v] >= dfn[u]
Day2 6.8
昨天打比賽然后今天頹廢去了
所以還是圖論
爭取完結圖論?(不可能的。。
最短路
Floyd
這個沒啥好說的\(f[i][j]\)表示從\(i\)到\(j\)的最短路,要先枚舉中介點\(k\),順序不能錯
因為這實際上是個三維的滾動數組
復雜度\(O(n^3)\)
Dijkstra
- dis[]距離數組,初始設成正無窮
- vis[]標記是否訪問過
- 一個優先隊列,里面扔一個pair是距離和標號,為了方便一般把距離取負,因為優先隊列返回最大值
1.不能處理帶負權的邊
優化后是\(O((n + m)\log n)\)
Bellman-Fold算法
就是SPFA復雜度的上界吧,還是SPFA好寫,過了
復雜度\(O(nm)\)
K短路
會玄學A*可以水過很多題啊
有一個論文算法,關於可持久化平衡樹維護最短路樹的
6.19 upd
差分約束
如果我們有一些限制形如\(x_{a} - x_{b} <= d\)
那么我們可以把它聯系到最短路上,如果\(b \rightarrow a\)有一條長度為\(d\)的邊,那么必然有\(x_{a} <= x_{b} + d\)
這樣我們求最短路,可以得到\(x_{a}\)的最大值
為什么是小於等於號卻是最大值呢,因為我們是從正無窮向下規約的
6.19 upd end
最小生成樹
prim
- dis[]表示每個點距離當前聯通塊距離最小是多少
- 每次選擇一個最小的點加入然后刪掉它的距離
復雜度\(O(n^{2})\),可以類似dij的優化但是優化之后不如kruskal
kruskal
- 直接把邊排序,然后加個並查集就好了
復雜度\(O(E \log E)\)
kruskal的正確性可以在很多思維題求最小生成樹中作為優化邊數的一種方法
二分圖
6.16upd:
二分圖的一些性質
|最大匹配數|=|最小頂點覆蓋|
最小頂點覆蓋的意思是我們選擇個數最少的點然后覆蓋所有的邊
我們考慮每一條邊一定連着一個被匹配的點,如果一條邊連着一個未匹配的點,那么這條邊也一定連着一條被匹配的點,否則匹配可以增加
|最大獨立集|+|最小頂點覆蓋|=頂點個數
最大獨立集就是選出一些點,這些點之間沒有邊相連
我們把最小頂點覆蓋去除了,那么剩下的頂點就沒有邊了啊
|最大匹配|+|最小邊覆蓋|=頂點個數
我們往最大匹配中加邊,就可以覆蓋所有頂點了,所以這個式子告訴我們,最小邊覆蓋就是選擇最大匹配的那些邊,然后加入新的邊將未匹配點鏈接起來
6.16 upd end
匈牙利算法
- matc[]數組表示\(Y\)部的點匹配的\(X\)部點是什么
- vis[]當前這個\(Y\)部點有沒有被遍歷過
- 每次清一遍vis,可以只連從\(X\)到\(Y\)的邊
復雜度\(O(n^3)\)
KM算法
- ex_l[]表示左部期望的最大值,初始是每個點能連的最大值
- ex_r[]表示右部期望的最大值,初始都是0
- slack[]表示斷層,每次在不能取的時候取最小
- 注意每次只把遍歷過的點的期望值,左邊減小,右邊加大,其余不動,因為我是失敗了以后給這個點能到的一些點加入至少一條可以供新選擇的邊
復雜度\(O(n^3)\)但常數非常小,適合夢想選手
2-SAT
- 建圖,縮點,Tarjan的標號的正序選擇即可
四種連邊
(A,B)不能同時取
那么選了A必須選B',選了B必須選A'
A->B' B->A'
(A,B)不能同時不取
那么選了A'必須選B,選了B'必須選A
A'->B B'->A
(A,B)要么都取,要么都不取
那么選了A'必須選B',選了B'必須選A',選了A必須選B,選了B必須選A
A'->B' B'->A' A->B B->A
(A,A')必須選A
A’->A
選了A'必須選A,保證了A拓撲序在前
解的多解依賴於拓撲序的多情況,這樣的話去除了A'在A前面的情況
Day3 6.9
今天寫樹
圓方樹
Tarjan縮點即可
具體題目具體分析了
基環樹
- 遍歷整個樹的時候遇到一條非樹邊,記錄下來,這條邊加樹邊一定是一個環
會出的題一般都是討論環上的如何操作
點分治
- 一個遍歷的函數
- vis[]表示這個點有沒當重心,遍歷的時候和求重心的時候只經過vis=0的點
- 一個計算重心的函數
- son[]重兒子
- fa[]父親
- size[]子樹大小
- 還有一個隊列
- 進隊列時清零
- 可能還會有對樹進行遍歷計算的函數
經常按照過重心和不過重心兩種方式考慮
多叉樹轉二叉樹,即左兒子右兄弟
動態點分治
實質非常暴力,就是在加入葉子的時候,走到重心的路徑,若遇到一棵樹大小超過一定比例的父親子樹的大小(設置一個分數,如0.7),則重構之
斯坦納樹
我怎么會這么個玩意
\(dp[i][S]\)表示特殊點的點集為\(S\),根為\(i\)的最小代價
然后轉移有兩種,一種是以同一個點為根
\(dp[i][S] = min(dp[i][T] + dp[i][S \oplus T],dp[i][S])\)
一種是從另一個根換過來
\(dp[i][S] = min(dp[j][S] + w[i][j],dp[i][S])\)
前面是子集枚舉,后面是暴力SPFA
Day4 6.10
學弟和小詹去廣州集訓了,zxg去六盤水支教了,全機房就剩我一個人了,感覺有點玄妙
今天寫網絡流
最大流
ISAP和Dinic的本質都是分層圖跑暴力
ISAP
- gap[]某個深度的層有多少個
- dis[]表示這個點到根的深度
- 每次回到根的時候都將根的dis++,如果失敗的話條件就是\(dis[S] >= MAXV\)
- 一層只能流向下一層,即\(dis[u] == dis[v] + 1\)才能往下流
- 若出現斷層\(gap[x] == 0\)則認為不能再增廣了
- 還有防止循環流的方法是記一個last表示這個點的邊集上次遍歷到哪里
Dinic
- 先BFS,再DFS
- BFS為了給整張圖標號,DFS就是在\(dis[u] == dis[v] + 1\)的時候跑流量
- BFS如果標號失敗就退出了
都要注意sumE = 1,使得邊的標號從2開始
最大權閉合子圖
即選擇一個點集,保證這些點集連向的所有點都在這個點集里
方法是源點向所有權值為正的點連一條邊,容量為權值,權值為負的點向匯點連一條邊,容量為權值的負數
如果問選擇什么點集就直接在殘余網絡中搜走到的點就行了
6.19 upd
有些情況下比如每個點有兩種選擇,每種選擇有不同的價值,則可以轉化為最小割問題
源點往i流一條容量為代價1的邊,i往匯點流一條容量為代價2的邊,中間可能還會有一些限制之類的,每個點代價1和代價2的總和減去網絡流就是答案,因為較小的那種方案會被割掉
6.19 upd end
最小費用最大流
一種是暴力跑SPFA增廣(已經可以糊弄過很多張圖了,何況貌似沒有復雜度下限一樣優秀的了)
一種是zkw費用流
- modlabel()函數修改標號,每次在遍歷過的邊選一條延伸向未遍歷的位置中價值最小的一條邊,然后累加,然后將所有遍歷過的點中延伸向未遍歷的位置中都減掉這個價值(具體實現就是將未遍歷的點的所有邊正邊減去反邊加上,如果邊兩端的點都遍歷過那么就相當於沒有操作)
- aug()暴力跑流,並且為每個沒有遍歷過的點標記,流量乘上累加的費用
這個算法常數比較小,每次至少選了一條邊進入可增廣的范圍
有限制的網絡流
無源匯上下界可行流
每條邊有一個上界up和下界low
- 把每條邊的流量改成\(up - low\)
- 由於每個點要流量平衡,我們計算每個點入邊的low就加上,出邊的low就減去
- 如果流量平衡了,就不管它,否則新建源點,往入邊流量多的點連一條流量為多余的入邊流量的邊,新建匯點,往出邊流量多的點連一條流量為多余出邊的點
有源匯上下界最大流
和無源匯的上下界可行流一樣,不過可以加上從匯點到源點容量為正無窮的邊,從超級源和超級匯跑一遍可行流
然后再從給定的源匯跑一遍最大流
忽然發現ISAP67ms,Dinic3000ms,不知道發生了什么
有源匯上下界最小流
和無源匯的上下界一樣,要先跑一邊無源匯的上下界可行流,把能滿足的都先跑滿,然后再加上從匯點到源點的一條邊,繼續跑無源匯的上下邊可行流,若合法,最后答案就是這條邊反向邊的權值
這道題ISap因為循環流會被卡。。。
Day5 6.11
不知道寫點啥。。。
分塊
一般是給出一個序列,把序列分成\(\sqrt{n}\)塊,每塊大小為$\sqrt{n} $
然后考慮塊間合並和塊內處理,一般要求合並和塊內都是\(O(1)\),然后最多遍歷\(\sqrt{n}\)個塊,代價是\(O(\sqrt{n})\)
\(O(n\sqrt{n}\log n)\)適合夢想選手(誤
一道著名的分塊題是求區間眾數,做法是對於每一個塊求前i個塊j個數的前綴和
然后再處理出以每個塊為開頭的塊眾數是什么,這一步是\(O(n\sqrt {n})\)的,即從每個塊開始把往后所有的數往以個桶里扔
這樣我扔兩邊的那些零碎的時候,就可以即時更新區間眾數了
莫隊
普通莫隊
即將左端點分塊,按塊排序,右端點從小到大排序
對於一個塊內右端點移動次數是\(O(n\sqrt{n})\),而左端點移動次數每次至多\(\sqrt{n}\),總移動次數也是\(O(n\sqrt{n})\)
按時間分的莫隊
塊的大小為\(n^{2 / 3}\)共有\(n ^{1 / 3}\)塊
左右端點都按塊排序,時間維從小到大排序
那么時間軸的移動,對於兩個不同的塊,移動次數是\(O(n)\),所以時間維移動了\(O(n^{5 / 3})\)
對於左端點的移動,每次在塊內最多是\(O(n^{2/3})\)一共\(O(n)\)次詢問所以是\(O(n^{5/3})\)
右端點的移動,右端點只在塊里是\(O(n^{5/3})\),左端點改變后右端點移動是\(O(n)\)的,但是不超過\(O(n^{4/3})\)
樹上莫隊
首先分塊變成了給樹分塊,方法就是用一個棧,如果遍歷一個兒子后棧中元素增加的量超過了我要分的塊,注意要先把兒子能分的塊分完了,再把父親扔進去
然后我移動兩個指針就是
先把\(u,v\)的lca取反
將\(u\)移動到目標節點\(tu\),然后把\(lca(u,tu)\)不動
\(v\)同理
然后將\(tu,tv\)的\(lca\)取反
Day6 6.12
忘了說,這是一件要堅持二十四天的事情,數字比較吉利
每天寫一點,看看能寫到哪吧。。
有點不想在學校待着,只有我一個人……
普通分治
就是考慮能不能分成左右兩個子問題,把合並控制在\(O(n)\)或者\(O(n \log n)\)級別
平面分治
例如,求平面最近點對
我們把平面分為兩部分使得每一部分點數相等
然后我們可以用兩邊計算的最小值作為中間合並時最小距離的限制
實際上這樣限制下來對於左邊的一個點,右邊可能對答案有貢獻的點不超過6個
CDQ分治
CDQ分治主要解決的是三維偏序問題
首先我們按照某一維排序
我們在每一層計算左邊對右邊的貢獻,即用在每一層按照另一維排序,並且將最后一維用數據結構維護,對於合法左邊的點把貢獻加給右邊
三維是\(O(n \log^{2} n)\)的,拓展到更高維度也是可以的,但是沒有暴力好,例如著名的五維偏序就是bitset。。。
三分
都帶個分字,我也就一起說了
這個東西其實沒什么好說的,就是對於一個單峰(谷)函數,分成三段,左右兩端點各走\(1/3\),看哪個值比較小(大),那么就里峰頂(谷底)更近一些,而不至於走過頭
有些函數可以說一下,例如絕對值函數取最大值(斜率相同)是比較常見的三分函數
\(x + \frac{1}{x}\)有名的對勾函數也可以考慮一下
二分
。。。。單調函數二分
當然也有些奇怪的應用,不一定是函數,而是一個集合至少擴增到哪個位置合法,是個隱蔽的單調函數
01分數規划
如果我選了\(k\)個數,求平均值最大,設平均值為\(g\)的話,那么可以得到
如果我們二分一個平均值看合不合法呢???
如果
我們只要把\(k\)移過去可以得到
而這個\(k\)其實是我們選擇的數的個數
那么呢我們再考慮一個轉化
這樣的話,我們只要選一個數就減一下\(g\),如果能得到\(\geq 0\)的值證明這個平均值合法
6.16upd:有些時候需要大於0
Day7 6.13
計算幾何(我瘋了我為啥要開這個。。。)
叉乘
\(x_{1}y_2 - x_{2}y_{1}\)
得到的是兩個向量的有向面積,逆時針正順時針負
點積
\(x_{1}y_{1} + x_{2}y_{2}\)
得到的是\(|a||b|cos<a,b>\)
向量模板
struct Point {
db x,y;
Point(db _x = 0.0,db _y = 0.0) {
x = _x;y = _y;
}
friend Point operator + (const Point &a,const Point &b) {
return Point(a.x + b.x,a.y + b.y);
}
friend Point operator - (const Point &a,const Point &b) {
return Point(a.x - b.x,a.y - b.y);
}
friend db operator * (const Point &a,const Point &b) {
return a.x * b.y - a.y * b.x;
}
friend db dot(const Point &a,const Point &b) {
return a.x * b.x + a.y * b.y;
}
friend Point operator * (const Point &a,db d) {
return Point(a.x * d,a.y * d);
}
db norm() {
return sqrt(x * x + y * y);
}
}P[MAXN];
點在線段上
friend bool InSegment(const Line &s,const Point &b) {
db d = (b - s.a) * (s.b - s.a);
if(!dcmp(d,0.0)) return false;
return dot(s.a - b,s.b - b) <= 0;
}
直線相交
一條直線可以用兩個點表示
friend Point GetInsect(const Line &s,const Line &t) {
db S1 = (t.a - t.b) * (s.a - t.b);
db S2 = (t.b - t.a) * (s.b - t.a);
return s.a + (s.b - s.a) * (S1 / (S1 + S2));
}
對於線段相交只需要判這個點在不在這個線段上就好了
點在多邊形內
做一條平行於x軸或y軸的射線,若交點為奇數則在內,偶數則不在內
凸包
選擇左下角的點,最下的同時最左或最左的同時最下,這個點一定在凸包上,然后把其余點按照極角排序,極角一樣的長度大的在后面
然后只需要用一個棧,看看新加入的這個點加進去后凸包是不是凸的
旋轉卡殼
一般就是如果枚舉點逆時針移動,最優答案也逆時針移動,這就是旋轉卡殼了
半平面交
每條直線按照平面內的幅角排序
Gter是如果\(a > b + eps\)返回真
for(int i = 1 ; i <= tot ; ++i) {
while(ql < qr) {
if(Gter(0.0,(S[i].b - S[i].a) * (p[qr - 1] - S[i].a))) --qr;
else break;
}
while(ql < qr) {
if(Gter(0.0,(S[i].b - S[i].a) * (p[ql] - S[i].a))) ++ql;
else break;
}
q[++qr] = S[i];
if(ql < qr) {
if(dcmp(q[qr].d,q[qr - 1].d)) {
if(Gter((q[qr].b - q[qr].a) * (q[qr - 1].a - q[qr].a),0.0)) --qr;
else q[qr - 1] = q[qr],--qr;
}
}
if(ql < qr) p[qr - 1] = Cross_Point(q[qr],q[qr - 1]);
}
while(ql < qr) {
if(Gter(0.0,(q[ql].b - q[ql].a) * (p[qr - 1] - q[ql].a))) --qr;
else break;
}
p[qr] = Cross_Point(q[qr],q[ql]);
求凸包公切線
把凸包每個點以左下角為起點按極角排序,看看在哪兩個點之間,然后往兩邊二分即可
具體實現很麻煩,題PKUWCD2T3出過,但是題目到現在都沒公布
給cy寫過一個凸包切點的流程,現在粘到這里吧
我字怎么那么丑,為什么,枯了。
Day8 6.14
要不要開數據結構呢。。。
先不開,等下周復習吧(咕咕咕
今天是各種類型的動態規划
動態規划
眾所周知,動態規划只需要腦子就夠了
插頭DP
實際上是狀壓加hash壓縮輪廓線
樹型DP
看看能不能分割成子樹中的問題
通常有一類dp需要在狀態中加入假定的未來狀態,如JLOI / SHOI2016偵查守衛
還有線段樹合並等優秀方法
背包dp
這個會的話就很簡單
看不出來的例如十二省聯考的皮配(像我)只能墊底了。。
區間dp
通常是\(n^{3}\)的,可能會使用bitset
一般是\(f[i][k]\)和\(f[k + 1][j]\)與\(f[i][j]\)的合並
數位dp
通常都會加入一維\([0/1]\)表示判斷是否和給定的上界相同,如果有下界通常會轉化乘\(F(R) - F(L - 1)\)的前綴和容斥形式,也有把上下界是否相等一起壓進狀態里的
一些優化方法
矩陣乘法
我喜歡類似Floyd那樣的更新,也就是\(a\rightarrow b\) 乘上\(b \rightarrow c\),可以更新\(a \rightarrow c\)
不僅僅是線性齊次遞推式可以更新,+和取max也是可以更新的
\(a\rightarrow c = max(a\rightarrow c,a\rightarrow b + b\rightarrow c)\)
並且也滿足結合律
1D/1D動態規划優化
單調性優化
\(f(x) = min_{i = 1}^{x - 1} \{f(i) + w[i,x]\}\)
滿足對於\(x < y\),最優的決策點\(k(x) < k(y)\)
這里有一點要注意的!
就是如果對於\(x\),\(i\)是最優的,那么不代表\(i + 1\)轉移到\(y\)比\(i\)轉移到\(y\)更優
因為有可能是,優 非常差 最優
那么算法的流程其實是這樣的,我們一開始只有\(f(0)\)於是這些就設成
000000000000000
然后我們計算出\(f(1)\)
會更改為
001111111111111
得到2可以改為
001111112222222
就是記錄每個連續段,得到一個數二分這個數可以從哪個位置開始更新,如果可以覆蓋掉整個連續段就跳過
單調隊列優化
\(f(x) = min_{k = b[x]}^{x - 1}\{g(k)\} + w[x]\)
容易發現我們要做的就是區間取最小值
單調隊列很容易可以實現這一點
斜率優化
$f(x) = min_{i = 1}^{x - 1} {a[x]f(i) + b[x]g(i)} $
由於是二元組,我們把它畫在平面直角坐標系上
設\(f(i) = x\),\(g(i) = y\),\(f(x) = P\)
P = ax + by
\(y = -\frac{a}{b}x + \frac{1}{b} P\)
很容易發現就是一條斜率固定的直線,從負無窮上移,第一個碰到的點就是答案
我們只要維護一個下凸殼就可以做到了
若斜率有單調變化,我們可以用第二種形式做到單調隊列優化
如果沒有則二分凸包
Day 9 6.15
眾所周知sjq是個頹廢的女孩子
所以她昨天頹廢去了,今天(6.16)打算補兩天
FFT
這個大家耳熟能詳就是推不出來卷積式子
具體細節有點贅余(畢竟是復習),所以直接上個板子好了
for(int i = 1,j = L >> 1; i < L - 1 ; ++i) {
if(i < j) swap(p[i],p[j]);
int k = L >> 1;
while(j >= k) {
j -= k;
k >>= 1;
}
j += k;
}
for(int h = 2 ; h <= L ; h <<= 1) {
int wn = W[(MAXL + on * MAXL / h) % MAXL];
for(int k = 0 ; k < L ; k += h) {
int w = 1;
for(int j = k ; j < k + h / 2 ; ++j) {
int u = p[j],t = mul(w,p[j + h / 2]);
p[j] = inc(u,t);
p[j + h / 2] = inc(u,MOD - t);
w = mul(w,wn);
}
}
}
一開始的二進制翻轉要設置成\(i < L - 1\)
然后\(h\)是步長,最底層是\(2\)
\(k\)是每一塊的起點
\(j\)遍歷這一塊的前一半,和后一半的依次進行蝴蝶操作
如果是逆DFT,那么要除上模長
單位復根的本質是把一個單位圓\(2^{k}\)等分,而復數相乘的結果是模長相乘,幅角相加
NTT
換成原根就好了,快樂
MTT不會,再見
FWT
tf表示正變換,utf是逆變換
異或卷積
\(tf(A) = (A_{0} + A_{1},A_{0} - A_{1})\)
\(utf(A) = (\frac{A_{0} + A_{1}}{2},\frac{A_{0} - A{1}}{2})\)
或卷積
\(tf(A) = (A_{0},A_{0} + A_{1})\)
\(uft(A) = (A_{0},A_{1} - A_{0})\)
注意對於或卷積通常有一類題需要在每一次卷積用$2^{n} $的復雜度還原出\(2^{n} - 1\)的值(就一個位置)
方法就是有奇數個0加上這個位置的值,偶數個0減去這個位置的值
與卷積
\(tf(A) = (A_{0} + A_{1},A_{1})\)
\(utf(A) = (A_{0} - A_{1},A_{1})\)
來一個FWT的板子(異或)
void FWT(int *a) {
for(int i = 1 ; i < (1 << N) ; i <<= 1) {
for(int j = 0 ; j < (1 << N) ; j += (i << 1)) {
for(int k = 0 ; k < i ; ++k) {
int t0 = a[j + k],t1 = a[j + k + i];
a[j + k] = inc(t0,t1);
a[j + k + i] = inc(t0,MOD - t1);
}
}
}
}
void IFWT(int *a) {
for(int i = 1 ; i < (1 << N) ; i <<= 1) {
for(int j = 0 ; j < (1 << N) ; j += (i << 1)) {
for(int k = 0 ; k < i ; ++k) {
int t0 = a[j + k],t1 = a[j + k + i];
a[j + k] = mul(inc(t0,t1),Inv2);
a[j + k + i] = mul(inc(t0,MOD - t1),Inv2);
}
}
}
}
FMT
來自VFK的論文。。
本質是或卷積,然鵝。。。代碼短啊!!
本質上是求\(f[S] = \sum_{T \in S} g[T]\)
用一個dp實現,我們按位考慮,從最低位開始數i - 1位已經考慮完了,我把第\(i\)位與它不同加進來
逆變換改成減掉就好了
void FMT(T *a,T ty) {
for(int i = 1 ; i < L ; i <<= 1) {
for(int j = 0 ; j < L ; ++j) {
if(j & i) {
a[j] = a[j] + ty * a[j ^ i];
}
}
}
}
然后是集合冪級數,我第一次做到這題還是WC2018(然鵝不會就是了
我們要求的是這個東西
\(h[S] = \sum_{T \in S}g(T)f(S \oplus T)\)
我會子集枚舉!!!
但是我們要會一個\(n^{2}2^{n}\)的算法才可以獲得滿分
設一個多項式\(g(S)x^{|S|}\),最后答案就是\(h(S)x^{|S|}\)
具體是設一個二維的數組,\(g[i][S]\),然后進行\(FMT\),點值相乘的時候\(n^2\)枚舉\(i,j\)用\(g(S)f(S)x^{i}x^{j}\)更新\(h(S)x^{i + j}\)最后再每一層都卷回去就好了
Day 10 6.16
眾所周知sjq是個肥宅的女孩子
所以她補兩天的感覺很累,打算寫一些邊角余料的知識點糊弄過去
整體二分
和cdq分治很像?但不是一個東西啦
Solve(int l,int r,int ql,int qr)
表示我二分的區間是\(l,r\),而詢問的區間是\(ql,qr\)
復雜度?當然是\(q \log V\)的,\(V\)是值域,因為每個詢問往下走都走了\(\log V\)層
虛樹
(我應該放到圖論那里的)
建樹點的個數應該是2倍的詢問點(上界)
建樹方法是按dfn排序后,維護右鏈,求插入點和棧頂點的lca,如果棧頂深度大於插入點則不斷彈出,在最后一個深度大於等於插入點的地方可能會被修改lca
之后如果lca不存在則插入lca
注意如果不需要樹而需要虛樹的邊長和,直接把虛樹按照dfn排序后,相鄰兩個點距離相加就是二倍的虛樹邊長和
void build_auxtree(int aN) {
num=aN;
sort(aux+1,aux+num+1,cmp);
stk[top=0]=0;
siji(i,1,aN) {
int u=aux[i];
if(!top) {
stk[++top]=u;
faAux[u]=0;
}
else {
int Lca=lca(stk[top],u);
while(dep[stk[top]]>dep[Lca]) {
if(dep[stk[top-1]]<=dep[Lca]) {
faAux[stk[top]]=Lca;
}
--top;
}
if(Lca!=stk[top]) {
faAux[Lca]=stk[top];
aux[++num]=Lca;
stk[++top]=Lca;
}
stk[++top]=u;
faAux[u]=Lca;
}
}
sort(aux+1,aux+num+1,cmp);
}
倍增求lca
\(fa[i][j]\)表示第\(i\)個點往上走\(2^{j}\)步是哪個點,若不存在則為0
把兩個點調整到同一深度,然后同時往上跳
(可以同時維護鏈上最小值,很好用)
ST表求lca
維護一個序列,求dfn序的同時在遍歷完每個兒子之后把父親加進去,兩點之間的lca就是兩點之間的最小深度的點,可以做到\(O(1)\)查詢
Day11 6.17
今天開始數據結構吧
第一天就瞎寫一些簡單的
按照這個進度我覺得我要復習不完了(因為頹)
單調棧,單調隊列
這個真的是數據結構么。。。
單調隊列可以實現一個區間內的最大值從遠到近排序(右端點遞增,左端點遞增)
而單調棧實現的差不多
單調隊列像一個開口的單調棧
並查集
int getfa(int u) {return fa[u] == u ? u : fa[u] = getfa(fa[u]);}
可以帶權維護很多東西,例如食物鏈那題
堆
priority_queue默認返回最大值
手寫堆的方法是把新加入的數放在最后一個,然后如果根小於它,則交換它和根
樹狀數組
struct BIT {
int tr[MAXN],s;
int lowbit(int x) {return x & (-x);}
void insert(int x,int v) {
while(x <= s) {
tr[x] += v;
x += lowbit(x);
}
}
int query(int x) {
int res = 0;
while(x > 0) {
res += tr[x];
x -= lowbit(x);
}
return res;
}
};
可以維護一個前綴和,有了前綴和的同時可以查詢區間和
單點修改
減掉原來的,直接插入
區間修改,單點查詢
給一段區間\([l,r]\)加\(d\),相當於在\(l\)處加上\(d\),在\(r + 1\)處減去\(d\)
jiukeyile然后查詢一段前綴和即可獲得單點的值
區間修改,區間查詢
我們還是考慮區間加之后如何得到前綴和,前綴和相減就是區間和
還是把加改成差分,一個\(p\)加上\(d\)的形式,給前\(r\)個數帶來的貢獻是
\((r - p + 1) * d\)
我們可以用兩個樹狀數組,一個維護一個前綴里加上過的差分的和,一個維護\((p - 1) * d\)的和,就可以了
二維樹狀數組
while寫兩層即可實現,可獲得一個\([1,x]\)和\([1,y]\)的矩形內的值的前綴和
前綴最大值
更新log個位置的最大值即可,用它只是因為太好寫了,寫個線段樹不愉快
hash表
就是類似邊表一樣,算完hash之后再給這個值取模一個較小的數扔進邊表里,扔的是這個數的取模前的值
Day12 6.18
線段樹
這個太普遍了,而且有很多變種,有你從未體驗過的全新玩法
通常可以優化一類dp,例如把一段數分成兩個集合\(dp[i][j]\)表示第一個集合的最后一個位置,和第二個集合的最后一個位置
不說了,這個會寫就很簡單,不會寫的題死也想不到這么維護的
李超線段樹
平面上有很多條直線,然后詢問一個位置的最小值
我們每個區間維護一個優勢線段,就是這個區間內暴露最多的線段
如果新加入的線段兩端都大於優勢線段,就不要
否則會有一個交點,我們取較長的那個作為優勢線段,然后另一個額外處理
這樣一次是\(O(\log n)\)的
平衡樹
可以維護一個序列,並且可以取出一段序列,實現區間翻轉移動之類的操作
我一般寫無旋Treap
樹鏈剖分
- fa[] 表示父親
- son[]表示子樹大小最大的那個兒子
- siz[]表示子樹大小
- top[]表示鏈頂
- dfn[]表示dfs序,idx用來增加dfs序
- dep[]表示深度
每次往深度大的鏈頂跳
長鏈剖分
選擇深度大的一個兒子進行剖分
可以在有關深度的復雜度做到線性
Day13 6.19
今天打包字符串類數據結構(和一些算法)
kmp
求一個\(nxt\)數組,\(nxt[i]\)表示\([1,nxt[i]]\)的字符串和\([i - nxt[i] + 1,i]\)的字符串相等
求法是找到從\(nxt[i - 1]\)開始跳\(nxt\)鏈,如果鏈上某一個位置\(p\)使得\(s[p + 1] = s[i]\)則\(nxt[i] = p + 1\),否則\(nxt[i]\)就是0
manacher
這個用來求最長回文子串
復雜度$ O(n)$
我們為了方便,可以給每個字符中間插入一個特殊字符,來同時考慮奇數回文串,和偶數回文串
設\(r[i]\)為\(i\)向兩邊擴展的最大長度使得\([i - r[i] + 1,i + r[i] - 1]\)是一個回文串
我們記錄一個擴展到最遠的位置\(mx\),和這個位置的中心\(p\)
當我們遇到下一個位置\(i\),可以用\(min(mx - i + 1,r[2 * p - i])\)來作為初始的\(r[i]\)更新
這樣每次\(mx\)不降,是\(O(n)\)的
擴展kmp
這個用來求每個位置和1的最長公共前綴
相當於砍了一半的manacher,我們也記錄擴展到最遠的位置\(mx\)和一個開頭\(p\)
新的位置\(i\)用\(min(mx - i + 1,r[i - p])\)作為擴展的位置
最小表示法
求循環串的最小表示法
用兩個指針\(i,j\)和這兩個開頭能匹配的長度\(len\)
若\(i\)失配了,則\([i,i + len]\)都是不合法的位置
\(j\)失配同理
如果\(i,j\)相等則把其中一個+1
如果有一個大於字符串長度則退出
Lyndon分解
一個字符串是Lyndon word當它是自己所有后綴中的最小值
如果字符串\(s\)和字符\(c\)組成\(sc\)是一個Lyndon Word的前綴,那么\(d > c\),則\(sd\)是個Lyndon Word
維護上一個未分解的位置是\(last\),看last開頭的下一個Lyndon 串是否被循環構造
int s = 1,t = 2,last = 1;
while(last <= LEN) {
s = last;t = s + 1;
while(1) {
if(str[s] == str[t]) ++s,++t;
else if(str[s] < str[t]) s = last,++t;
else {
do {
last += t - s;
out(last - 1);putchar(' ');
}while(last <= s);
break;
}
}
}
7.9 upd
如何快速求出每個后綴的Lyndon分解
首先需要把后綴排序(當然更方便的是直接二分哈希求lcp,我就是這么寫的,會多一個log,冬令營的課件上似乎有\(O(n)\)做法,不太懂)
我們用一個棧,求出后綴\([i + 1,N]\)的lyndon分解后,我們新加一個字符\(s[i]\),如果這個串比前一個lyndon串小,就把這個串和后一個串合並起來
因為若\(u,v\)都是lyndon串,\(u < v\),則\(uv\)也是lyndon串,因為v嚴格小於自己所有后綴,\(u < v\),u也會小於新加進來的所有后綴
7.9 updend
trie樹
每個點連出的邊兩兩不同,邊權是字符
AC自動機
為每個trie樹上的節點找一條前綴節點表示若在此處失配能同時匹配的最大的深度到哪,顯然這個節點是唯一的
新建一個0號點往根連字符集的邊
這個節點就是父親節點的前綴節點的該邊,若不存在則繼續跳前綴節點,類似kmp
或者一開始就補齊所有邊
AC自動機的每個節點也可以唯一的表示多模式串匹配中的狀態
后綴自動機
每個節點維護
- len代表維護的字符串是根節點到這個節點路徑中長度為len的一條
- nxt表示出邊
- cnt 表示這個點被統計次數
- par表示以這個點為結束點的后綴中最長的在之前出現過的字符串的代表節點
last表示當前最后一個節點
每次新加一個節點,則在last鏈中沒有該字符的節點上連一條邊
若last遍歷到空,則當前節點的par是根
若沒有,則看當前指針p該字符的出邊指向節點q是否是len[q] = len[p] + 1
否則復制一個q,長度設置為len[p + 1],cnt設乘0,其余不變,q與當前節點的par都設置成復制的節點
把last設置為當前節點
后綴樹
反串的后綴自動機的par樹等於正串的后綴樹
7.12 upd
回文樹
建上兩個根,一個表示偶數點回文串的根,一個表示奇數點回文串的根,偶數點回文串的根的父親節點是奇數點回文串的根
然后我們初始在偶數點回文串的根,如果失配則不斷跳父親
如果存在一個回文串,即當前節點維護的回文串長度為len,當新加一個節點后,存在\(s[i - len - 1] == s[i]\),那么就可以從當前節點延伸出去一條值為\(s[i]\)的邊
那么如何更新父親節點
同樣的類似AC自動機,父親節點的后綴節點的該邊,我們也不斷的跳父親鏈直到找到一個使得又一個\(s[i - len - 1] == s[i]\)的地方,如果父親是奇數根則默認這個點的父親是偶數根
可以在前面插入,也可以在后面插入,是類似的,具體可以參考
而回文樹的fail樹,從底到根表示以這個位置結尾的回文串各是什么
板子:APIO2014回文串
CF上有一個技巧
感覺這個技巧就是應用了以一個位置i為結尾的回文串,構成的回文串長度形成了\(\log n\)個等差數列
然后一個等差數列,若首項為\(a\),公差是\(d\),則可以轉移的位置是\(i - a - d\),\(i - a - 2d\),\(i - a - 3d\)...這一連串的位置,然而我們可以從\(i - d\)的位置維護一個\(series-ans\),這個位置的\(i - a - d\)就是\(i\)的\(i - a - 2d\)
關於為什么不是\(i - a\)?因為不斷向上找最后找到的一定是偶數根或者奇數根,沒有長度,對統計沒有意義,所以最后所有位置還是會被考慮到
7.12 upd end
Day14 6.20
我jio得要復習不完了(還有十天……我數論還沒開)
主席樹(可持久化線段樹)
一個是新樹的根,一個是舊樹的根,把新樹的根分配一個節點,然后復制的和舊樹一模一樣,再進行修改
將修改遞歸到左兒子或右兒子繼續操作
這是單點修改
對於主席樹的區間修改來說,要用樹狀數組套主席樹,每次修改log個樹根,查詢的時候也是
可持久化平衡樹
Spilt的時候每次新建一個節點,Merge的時候也是
炸內存愉快
可持久化Trie
每次也是新建一個節點,把之前節點的信息都復制過來
左偏樹
可並堆,具有堆的性質(根的權值大於兩個兒子)
每個節點額外維護一個\(dis\)表示到最遠的左兒子的距離
每次優先和右兒子合並
如果右兒子的dis大於左兒子的dis就交換
K-D樹
維護一個平面上的點集
每次兩維(或更多維)交替着來,每次按照需要划分的那一維排序,然后中位數的點作為這個節點維護的點,之后分到兩邊處理
可以維護很多東西,通常是維護這個區間點集橫坐標和縱坐標的范圍,便於剪枝
正交范圍復雜度是\(O(n^{\frac{k - 1}{k}})\),通常\(k = 2\),復雜度$O(\sqrt{n}) $
LCT
LCT里分實邊和虛邊,每個節點往下只連一條實邊,其余都是虛邊
實邊把樹分成了好幾條鏈,我們把這些鏈當做一個平衡樹維護,而這個平衡樹的頂的父親就是虛邊連接的父親(實際上,在樹中這個父親是這個平衡樹最左節點的父親,而非根的父親)
用Splay維護每條樹鏈,用Splay實現一個把每個點轉到樹根的操作
Access(x)
實現的功能是構造一條x到根的路徑
1.如果x有實邊,斷掉,方法是把x轉到根,然后斷掉右兒子
2.找到路徑的父親(實際上是Splay中最左節點的父親),把父親轉到根,斷掉右兒子,換成x所在splay的根(也就是切換偏愛邊)
3.平衡樹的根不是空節點,說明沒有連到根,繼續
Findroot(x)
找到x所在樹的根
先Access(x),然后找到最左節點
MakeRoot(x)
先Access(x),然后該平衡樹翻轉
Link(x,y)
把x變成根,然后加一條虛父親到y
Cut(x,y)
把x變成根,然后Access(y),把y旋到根,此時x是y的左兒子,斷掉即可(同時修改y的左兒子和x的父親
Select(x,y)
把x變成根之后Aceess(y)即可得到一棵平衡樹
一般有些題不是特別需要MakeRoot,可以簡化一下代碼
如果需要選兩個點之間的鏈就拆成兩段做就好了
楊表
從上到下每一行長度遞減,如果是遞增的樣表,則每個數要大於它上面的值和左邊的值
維護方法比較暴力,就是每一行二分一個插入位置,如果是空的就插入,如果有值,先插入,再把這個值扔到下一行去繼續插入
有個結論是把排序方法反過來可以得到樣表的轉置
還有一個公式是鈎長定理
有n個數的楊氏矩陣的個數為(固定了形狀)
設每個格子鈎子長度為\(h(x,y)\),鈎子長度是每個點右邊的格子數+下邊個格子數+1
答案是\(\frac{n!}{\prod h(x,y)}\)
還有半標准楊表(行單調不增,列單調減)
\(\prod_{h(i,j)} \frac{r + j - i}{h(i,j)}\)
其中\(r\)是值域的大小
Day15 6.21
埃氏篩
復雜度\(n \log\log n\)
每遇到一個質數,就用這個質數的倍數去更新區間里所有的數
對於需要篩區間里的質數且區間長度較小而左右端點較大時就只能用埃氏篩而不是歐拉篩
例如
\(10^9 \leq l \leq r,r - l \leq 10^{7}\)
歐拉篩
對於每個數\(i\),枚舉質數\(p\),把\(ip\)標記為合數,如果\(p | i\)則退出
這樣每個合數只在最小的質數那里被篩了一次,是\(O(n)\)的
可以用來預處理積性函數,互質的情況顯然乘起來就好,不互質考慮這個質數帶來的貢獻即可
擴展歐拉定理
若\(a,p\)互質,且\(p\)是質數可以得到\(a^{p - 1} \equiv 1 \pmod p\)
若\(a,m\)互質,\(m\)不一定是質數,可以得到\(a^{\varphi(m)} \equiv 1 \pmod m\)
若\(a,m\)不互質,若\(c > \varphi(m)\),可以得到\(a^{c} = a^{c \% \varphi(m) + \varphi(m)} \pmod m\)
擴展gcd
求\(ax +by = g\)的一組解
首先我們可以轉化成求\(\frac{a}{g}x + \frac{b}{g}y = g\)於是我們只剩下\(ax +by = 1\)的方程要解了
考慮gcd的求法
若存在一組\(ax + by = 1\)
上一次遞歸中求出了\(bx' + a \% by' = 1\)
可以得到
於是\(x = y'\),\(y = x' - \lfloor \frac{a}{b} \rfloor y'\)
可以用來求逆元,求線性同余方程
中國剩余定理
如果有一組方程
\(x \equiv a_{1} \pmod {m_{1}}\)
\(x \equiv a_{2} \pmod{m_{2}}\)
\(\cdots\)
\(x \equiv a_{n}\pmod {m_{3}}\)
其中\(m_{1}m_{2}...m_{n}\)兩兩互質
我們設\(M = m_1m_{2}\cdots m_{n}\)
設\(M_{i} = \frac{M}{m_{i}}\)
求出\(M_{i}t_{i} \equiv 1 \pmod {m_{i}}\)
\(x = a_{1}t_{1}M_{1} + a_{2}t_{2}M_{2} + \cdots + a_{n}t_{n}M_{n}\)
在\([1,M - 1]\)內有且只有一個解,剩下的每個解都要加上M
線性同余方程
\(ax \equiv b \pmod m\)
可以這么認為
\(ax = b + mk\),k是任意整數
這個方程有解的條件是\(gcd(a,m) | b\)
於是可以把兩邊同時除以\(gcd(a,m)\)是等價的
\(\frac{a}{g}x = \frac{b}{g} + \frac{m}{g}k\)
然后把這個當成新的\(a,b,m\),這個時候認為\(a,m\)互質,求出\(a\)在\(m\)意義下的逆元,
於是\(x \equiv \frac{b}{a} \pmod m\)
於是我們可以認為所有的形式都是
\(x \equiv a \pmod {m}\)
然后怎么求這個呢
假如
\(x \equiv a_{1} \pmod {m_{1} }\)
\(x \equiv a_{2} \pmod{m_{2} }\)
\(x = a_{1} + km_{1}\)
\(x = a_{2} - hm_{2}\)
\(a_{1} + km_{1} = a_{2} - hm_{2}\)
\(km_{1} + hm_{2} = a_{2} - a_{1}\)
為了解這個方程方便,我們還是可以兩邊都除上\(gcd(m_{1},m_{2})\)
然后我們解得了一個\(k\),於是新的\(x \equiv a_{1} + km_{1} \pmod {lcm(m_{1},m_{2})}\)
於是這么兩兩合並下去即可
Day16 6.22
由於sjq過於頹,她把該一天寫完的分成兩天寫完,並且理所當然的認為復習不完就復習不完,國賽打鐵就打鐵吧
高次不定方程
\(A^{x} \equiv B \pmod C\)
BSGS求解
基礎版\(A,C\)互質
我們把\(A^{0}\)到\(A^{\varphi(C) - 1}\)分成\(m = \sqrt{C}\)個一組
對於第一組,暴力處理出組內\(A^{y}B\)的值,存在一個哈希表里
剩下組中的值相當於\(A^{im - y} \equiv B \pmod C\)
我們把\(A^y\)移過去,就是\(A^{im} \equiv BA^{y} \pmod C\),於是每組可以只詢問一個\(A^{im}\)看是否存在
非基礎版,\(A,C\)不互質
\(A^{x} \equiv B\pmod C\)
\(A^{x} + Ck = B\)
我們每次除掉一個gcd(A,C),若\(B\)不能整除這個gcd,則無解
\(\frac{A}{d_{1}} A^{x - 1} + \frac{C}{d_{1}} k = \frac{B}{d_{1}}\)
這個時候\(\frac{C}{d_{1}}\)可能還和\(A\)不互質,我們就一直除下去
直到互質為止
設\(C_{n} = \frac{C}{d_{1}d_{2}\cdots d_{n}}\)
\(B_{n} = \frac{C}{d_{1}d_{2}\cdots d_{n}}\)
\(D = \frac{A}{d_{1}d_{2}\cdots d_{n}}\)
得到了
\(A^{x - n} \equiv \frac{B}{D} \pmod C\)
於是我們又回到了剛剛那個問題
原根
每個質數內可以找到一個數\(g\)
使得\(g^{1}\)到\(g^{p - 1}\)構成了\([1,P - 1]\)里的每個數
這個原根通常很小,可以暴力求,求得方法就是判每個\(a|P - 1\),a不為1
\(g^{\frac{P - 1}{a}}\)是否等於1,若等於1則g不是原根
N次剩余
\(x^{K} \equiv a\pmod P\)
為了簡單一些……只討論質數……非質數的實在過於難寫
可以參考這里
找到\(g^{t} = a\),這個用BSGS解
設\(x = g^{i}\)
求一個指標可以得到
\(g^{iK} \equiv g^{t} \pmod P\)
\(iK \equiv t \pmod {P - 1}\)
這個只需要解一個同余方程就好了
Day17 6.23
可能是一些線代相關?(得了吧你會線代嗎)
矩陣乘法之前好像說了……
高斯消元
有一組方程有\(n\)個變量,和\(n\)個方程
我們在第\(i\)次循環中,排名在\(i\)以后且\(x_{i}\)系數最大的方程,使其變成第\(i\)個方程
然后用這個方程消掉排名在\(i + 1\)及以后的方程第\(i\)項的系數
如果某一次發現\(x_{i}\)系數最大就是0,那么這個位置可以任意取值,題目里可能會有一些無解之類的可能是這種情況
行列式
行列式是一個排列\(p_{1},p_{2}...p_{n}\),求逆序對數為\(r(P)\)
求\(\sum (-1)^{r(p_{1},p_{2},p_{3}...p_{n})}a_{1,p_{1}}a_{2,p_{2}}...a_{n,p_n}\)
求行列式的一般方法是把矩陣通過基礎行變換或者列變換把矩陣消成一個上三角矩陣,然后對角線上的值相乘即可
具體的變換是
1.行列交換,行列式不變 就是\(a_{i,j}\)變成\(a_{j,i}\)(但是一般用不上這個)
2.行列式一行的因子可以提出 就是一行都除\(k\),求完這個行列式后再乘上\(k\)
3.兩行互換,行列式反號
4.將一行的倍數加到另一行上,行列式不變
好像寫的話只需要34就夠了
矩陣樹定理
入度矩陣對應的外向樹,出度矩陣對應着內向樹(都是指向父親的邊的事是出度或者入度)無根樹就是兩條有向邊都加上
有向樹必須刪掉根所在的那一行和一列,無根樹可以任意
然后對於這\(n - 1\)階的矩陣求一個行列式就行了,也叫主子式
矩陣求逆
建單位矩陣,和要求逆的矩陣同時進行求行列式用到的變換
注意要么只換兩行要么只換兩列,不能都換(不過一般你都是在換行……不會換列……)
目標是把要求逆的矩陣消成單位矩陣,此時的單位矩陣就是逆矩陣
線性基
就是每加入一個數,從最高位到最低位遍歷,如果有這一位就和這個數異或一下消掉
一個\(k\)個數組成的線性基可以構成\(2^{k}\)個不同的數\([0,2^{k} - 1]\)
帶修改的線性基可以通過額外維護一個二進制數表示這個數是由哪些數異或而成的
如果要修改第\(k\)個,且有包含第\(k\)個的數值為0,則可以用這個0和所有包含第k個數的值異或一下,再將修改后的值嘗試插入線性基
若包含第k個的數值為線性基中的數,則選擇最小的那個數,和前面的更新,把這個數刪除,然后嘗試插入線性基
Day18 6.24
天哪居然只剩7天了,真的復習不完了
今天是數論函數變換,由於我很喜歡這個東西,於是我決定去51nod刷點數論函數題再回來寫(然鵝窩根本做不出來那些題= =
莫比烏斯反演
\(g(n) = \sum_{d|n}f(d)\)
\(f(n) = \sum_{d | n}\mu(\frac{n}{d})g(d)\)
\(\mu\)函數大家很熟悉不再贅述
這個雖然可以暴力推式子證明,但是有比較好的思路就是容斥
\(g\)是什么,\(g\)是在質因數分解的意義下的一個高維前綴和
那么,我們把這個高維前綴和嘗試推掉其中的幾維,也就是這一維下標-1,如果推掉了奇數維就是+1,偶數維就是-1
這也就是\(\mu(d)\)的意義了,\(d\)是一定是若干個質數相乘
還有另一個形式!
\(g(n) = \sum_{n | d} f(d)\)
\(f(n) = \sum_{n | d} \mu(\frac{d}{n})g(d)\)
就是后綴和和前綴和的區別了,差不多的。。。
對於\(g(n) = \sum_{d | n}f(d)\)寫過一個很蠢的\(O(n \log \log n)\)求法,實際上很簡單。。但是裝模作樣的寫了一堆
積性函數
- 積性函數 對於兩個互質的數\(a,b\),滿足\(f(ab) = f(a)f(b)\)
- 完全積性函數 對於所有正整數\(a,b\),滿足\(f(ab) = f(a)f(b)\)
積性函數均有\(f(1) = 1\)
可以通過討論質數的指數冪應該的函數值從而得到整個函數值
狄利克雷卷積
\(h(n) = \sum_{d | n}f(\frac{n}{d}) g(d)\)
則\(h = f * g\)
有交換律,結合律,分配律,還有單位1
還有\(f,g\)如果都是積性函數,則卷積后的\(n\)也是矩形函數,這個比較常見……
\(\varepsilon(u) = [u == 1]\)就是單位1
然而數論題呢~其實沒怎么用到莫比烏斯反演,主要還是……用到了一些常用的卷積,當然如果你細心的話可以發現這些卷積可以通過莫比烏斯反演推導而來,然而這里直接寫了
\(d(n)=\sum_{d|n}1\)約數個數和
\(\sigma(n) = \sum_{d|n} d\)也就是\(d * 1\)
\(n = \sum_{d|n} \varphi(d)\) 這個的意義是\(n/d\)乘上和\(d\)互質的數,這樣就計算了和每個數和\(n\)gcd為\(n / d\)的方案數
這個時候反演一下可以得到
\(\varphi(n) = \sum_{d | n}\frac{n}{d}\mu(d)\)
\(\varepsilon(n)= \sum_{d|n} \mu(d)\)這是最常用的一個!因為它可以把一個判別式轉化成代數式!很神奇!
容易忘記的技巧
感覺技巧挺多的(然而我做題少QAQ)
\(i\)以內和\(i\)互質的數的的和是\(\frac{i \times \varphi(i)}{2}\),1除外,1的話就是1
n以內無平方因子數可以通過\(\sum_{i = 1}^{\sqrt{N}} \mu(i) \lfloor \frac{N}{i^{2}}\rfloor\)
杜教篩
假如我們有一個積性函數要求和
不會
假如我們有個積性函數,在和一個已知函數卷積的時候,卷出來的函數可以\(O(1)\)求和
嗯??????感覺事情變奇妙了
那么我們以最普通可愛的\(\mu\)來舉例吧
\(\varepsilon(n) = \sum_{d | n} \mu(d)\)
那么我們嘗試列一個式子
然后把這個式子改為枚舉一個\(t\),再枚舉\(i\),求的是\(it\)這個倍數在\(n\)以內的這樣的\(\mu(i)\)
然后就變成了
咦……如果設\(M(n) = \sum_{i = 1}^{n} \mu(i)\)
后面的就是
那么,只有\(t = 1\)的時候,才是我們要求\(M(n)\)
這樣的話,我們再轉換成
這樣的話,我們只要處理對后面的部分遞歸下去就好了,通常為了快一點我們會使用哈希,並且預處理出1e7以內的前綴和
好像還有高階小量啥的,不太懂,反正復雜度是\(O(n^{\frac{2}{3}})\)的
通常還可以用到的卷積有
\(n = \sum_{d | n}\varphi(d)\)可以用來求\(\varphi(n)\)函數的前綴和
還有……
\(n^{2} = \sum_{d|n}d\varphi(d) \frac{n}{d}\)
可以用來求\(d\varphi(d)\)的前綴和……
但是這個限制很大,只有有一個好求的卷積后的函數才可以快速出解……
min25篩
留個坑!一定補!我去翻zsy博客了!qwq
天哪我居然馬上要去國賽了還要學新知識點,感覺要廢
Day19 6.25
Day20 6.26
二項式反演
精妙無比但是根本用不上的原式
不精妙但是非常實用的另一種式子
不精妙但是非常實用的另一種式子2
這兩種式子分別代表什么呢
第一種是至多和恰好的轉化
設\(g(n)\)表示恰好有\(n\)個元素合法,\(f(n)\)表示至多有\(n\)種元素合法,則至多的每一種方案是從恰好\(i\)個中轉移過來的
舉個栗子
有k個方格,染上恰好n種顏色,每種顏色必須出現
這個時候,至多有\(a\)種顏色很好算,就是\(f(a) = a^{k}\)
這樣直接套進這個式子就可以算出來恰好了!
為了方便書寫,我把它叫至多,事實上你可以認為它叫“欽定”,或者“硬點”
因為真正的“至多”,就是\(f(n) - f(n - 1)\),這里顯然不是這樣
什么?為什么不是這樣
例如“欽定”填3種顏色,和“欽定”填2種顏色,填3個格子各有什么區別
第一種是
1 1 1 & 1 1 2 & 1 2 1 & 1 2 2
2 1 1 & 2 1 2 & 2 2 1 & 2 2 2
1 1 3 & 1 2 3 & 2 1 3 & 2 2 3
1 3 1 & 1 3 2 & 2 3 1 & 2 3 2
3 1 1 & 3 1 2 & 3 2 1 & 3 2 2
3 3 1 & 3 3 2
3 1 3 & 3 2 3
1 3 3 & 2 3 3
3 3 3
第二種是
1 1 1 & 1 1 2 & 1 2 1 & 1 2 2
2 1 1 & 2 1 2 & 2 2 1 & 2 2 2
顯然,三種顏色填三個格子方案數是\(3! = 6\)
然而\(27 - 8 \times \binom{3}{2} = 3\)
少到哪里了?
就少在,3個格子只出現了一種數,被這兩種情況減了兩遍
第二種是至少和恰好的轉化
你肯定會熟悉(至少0個 - 至少1個 + 至少2個....)的公式,請帶入\(k = 0\),你就會明白,它沒有組合數是因為\(\binom{i}{0} = 1\),於是,大家在做至少\(k\)個\(k\)不為0的時候,記得追根溯源,把無法省略的組合數補上
設\(g(n)\)是恰好\(n\)個的方案數,\(f(n)\)是至少\(n\)個的方案數
在這里順便介紹一種常見的容斥,含有上界的計數,即對於\(n\)個數,只能選\([0,a]\)之間,上界給定是\(S\),求方案數
方法就是枚舉至少有幾個數不合法,然后剩下的數隨便分配
當然,這里也不要當成真正的“至少”,你也可以叫“欽定”,或者硬點
第一類斯特林數
第一類斯特林數的組合意義是把\(n\)個元素放在\(m\)個環里
遞推方法是
組合意義就是,要么這個數新建一個環,要么插在任意一個數的前面
一點小性質
因為斯特林數本質是環排列,可以和置換對應
(翻了翻yyb的博客抄來了一點別的……事實上第一類我跟本不熟……)
其中\(x^{\underline{j}}\)認為是\(\frac{x!}{(x - j)!}\),不過我們要從\(x\)減下去那么乘,有些時候沒有逆元
證明(也是抄的)
利用數學歸納法\(n = 0\)時
然后
還有一個式子
可以類似的證明
第一類斯特林數的生成函數
從遞推式考慮,要么是\(m - 1\)變成\(m\),會有一個\(x\),要么是從\(n - 1\) 變成\(n\)會乘上一個\(n - 1\),從第一行往下推即可得到這個式子
於是我們就可以分治FFT求第一類斯特林數了!
(抄來的一點倍增方法)
\(F_{n}(x) = \prod_{i = 0}^{n - 1}(x + i)\),有\(F_{2n}(x) = F_{n}(x)F_{n}(x + n)\)
考慮用\(F_{n}(x)\)去求\(F_{n}(x + n)\)
第二類斯特林數
第二類斯特林數的組合意義是把\(n\)個不同的數放到\(m\)個相同的盒子里,且每個盒子必須有數
第二類斯特林數可以轉化成乘方
表示選了至多\(i\)個盒子放東西
這樣的話我們反演一下可以得到
第一個式子也是FFT預處理第二類斯特林數的方法
由於斯特林數在\(n < m\)的時候答案為0,所以可以第二個式子的上界也可以是n,在指數遠小於底數的時候作用很大
自然數冪和也可以用有關第二類斯特林數的一個公式
(斯特林反演不會呀qwq)
Day21 6.27
今天是一堆博弈論吧。。。(可能是開天辟地級別的……因為博弈論除了sg我啥也不會……)
(yyb的博客抄的真開心)
必敗態和必勝態
必敗態轉移一定會到必勝態,無法轉移到另一個必敗態
必勝態轉移會到至少一個必敗態
AtCoder上有一類題無需過多的博弈論知識,只需要找到必勝態必敗態,非常鍛煉腦洞能力
找的方法就是按照上面兩種方法分析,通常需要分析游戲結束時的狀態的性質
模型
巴什博弈(Bash Game)
取一堆石子有n個,每次至少取1個,最多取\(m\)個
分析
當\((m + 1) | n\)先手必敗,否則先手必勝
就是如果\((m + 1) | n\)的時候先手取什么,后手都可以跟一步使得總和是\(m + 1\)的倍數,后手必然勝利
否則先手取\(n \% (m + 1)\),轉化到必敗態
尼姆游戲(Nim Game)
有\(n\)堆石子,每堆石子有\(a_{i}\)個,每次可以選擇一堆取任意多個,不能取的人失敗
分析
所有石子異或的總和如果為0就是先手必敗,否則先手必勝
證明就是存在一堆石子含有異或和\(x\)的最高位,這個數\(k\)和\(x\)異或后一定小於\(k\),我們取那么多石子出來,石子的異或和就重新變為0
變形
- Bash + Nim 每堆取模\((m + 1)\)后再做\(nim\)
- 每次可以取k堆石子,轉化乘\(k\)進制下不進位加法,如果不等於0則先手勝
- 階梯博弈 每次可以將一堆石子移動到下一階,石子在0階不可移動,無法操作者輸
- 由於0階必敗,如果對手移動了一個偶數階梯上的石子,則順勢把這堆石子移到下一個偶數階梯上,游戲不變,如果對手移動了一個奇數階梯上的石子,就相當於石子沒了,所以我們只要統計奇數階梯上的石子來做一個普通的nim游戲
SG函數
對於一個游戲的必敗態,\(SG(X) = 0\),否則\(SG(X)\)是當前游戲可以到達的局面中未出現過的\(SG\)值的最小值
當所有游戲異或起來為0的時候先手必敗,否則先手必勝,證明類似nim游戲
Anti-SG游戲
有n堆石子,每次每人必須取走大於等於1個石子,拿走最后一個石子的人輸
分析
SJ定理
當所有單一游戲的SG值為0時游戲結束,先手必勝的條件是
游戲的SG值不為0,存在一個單一游戲的SG值>1
游戲的SG值為0,不存在一個單一游戲的SG值>1
Multi-SG游戲
也是普通的nim游戲,但是加上了可以把一堆石子分成兩堆,不能操作者輸
分析
對於分成兩堆的游戲打表后可以發現是
0 1 2 4 3 5 6 8 7 9 10 12 11
就是除了0以外每四個划分一下,然后第三個和第四個交換位置
Every-SG游戲
對於每個沒有結束的任何一個單一游戲,操作者必須進行一步操作,無法操作者輸
分析
看似和每個游戲的勝負沒有關系了,然而如果先手勝的話,那么走的次數一定是奇數次,證明這個游戲會讓后手無法操作,所以能贏的盡量贏,效果不會更差
然而對方也是這么想的,於是我們想讓對方能贏的局面快點結束
游戲實際上是兩兩獨立的,分開考慮
如果對於一個游戲,我在自己必勝態,我肯定希望這個游戲盡可能的慢點結束,如果我在必敗態,我想讓它盡快結束
於是我在必勝態選擇距離終點最遠的一個必敗態走,在必敗態選擇一個距離終點最近的必勝態走
這樣我一個狀態走到終點的步數通過類似這樣的min-max搜索實際上是確定的
而對於一個必敗態,走的步數是偶數,對於必勝態,走的步數是奇數,對於這樣的游戲,必勝的條件就是所有點走到終點步數的最大值是一個奇數
翻硬幣游戲
不是裸題……但是差不多的樣子?
每次可以翻轉一些連續的硬幣或者其他的什么約束,但是要求最右邊的硬幣必須正面朝上,無法進行者輸
分析
結論是所有正面朝上的硬幣單一存在時的sg函數的異或和
樹的刪邊游戲
輪流刪邊,每次保留和根節點連通的塊,不能刪者輸
分析
結論是一個樹的sg值是所有兒子的sg值+1后的異或和
具體證明可以類似數學歸納法那么證
證明,把每個兒子當成一棵樹加一條邊,設新加的邊為\(u,v\),\(u\)是\(v\)的祖先,若斷掉新加的邊,則sg值是0
否則斷\(v\)子樹中的邊,sg函數值為\([0,sg[v] - 1]\),從小到大取,使得\(v\)為根游戲狀態為0的那條邊,這條邊斷了之后,由於子游戲中存在一個狀態是斷掉新邊游戲狀態是0,則這個狀態給\(u\)為根的貢獻是\(1\)
使得\(v\)為根貢獻為1的斷邊狀態,子游戲中斷邊為0的狀態可以轉化為1,再加上斷掉新邊的狀態是0,則這個對\(u\)的貢獻變成了2
以此類推,這種情況游戲狀態就是\(sg[v] + 1\)
總而言之
博弈論這方面,自己推結論是不可能呢,這輩子 都不可能自己推結論的
數學數學又不好,又找不到必勝必敗態……
一打表就可開心了,打表不用動腦子,找規律比用腦子硬想感覺好多了,本來就沒有智商,還怎么做題呢,也只有打打表找找規律,才能勉強維持的了生活的樣子。
Day22 6.29
Lucas定理
\(\binom{n}{m} = \binom{n \% p}{m \% p}\binom{n / p}{m / p}\),其中\(p\)是質數
看到這個可以考慮數位dp,因為都是n和m在p進制下分解后按位求組合數
凸優化
如果隨着選的個數\(m\)遞增,答案構成了一個凸函數
而m是固定的,這個時候我們二分最后一次的斜率,選一個數就減少q,如果這個時候最大值恰好是選了m個則這個斜率就是答案
如果選了大於M個,則斜率需要縮小,否則需要增大
拉格朗日插值
給出平面上\(n\)個點值\((x_{i},y_{i})\),找到一個\(n - 1\)次多項式使得滿足這個多項式過這n個點
本質是一種構造
我們需要拉格朗日基本多項式
多項式\(\xi_{i}(x)\)在\(x = x_{i}\)的時候值為\(1\),在\(x = x_{j}\),\(j \neq i\)的時候是0
然后構造多項式
\(f(x) = \sum_{i = 1} ^{n} y_{i}\xi_{i}(x)\)
在橫坐標連續的情況下,通過\(O(n)\)計算出一個點的值
如果要求出這個多項式,可以通過\(O(1)\)求出下半部,\(O(n^{2}\log n)\)的復雜度求出上半部分
具體就是\(Solve(l,r)\)計算一個多項式表示沒有乘上\(i \in [l,r]\)的\((x - x_{i})\)的多項式是什么
然后分治下去
7.12 upd
分拆數
題意很簡單,就是求\(n\)拆分成若干個正整數的和有幾種方案
一個簡單的背包dp是\(O(n^2)\)的
那么還有一個\(n \log n\)的多項式求逆做法(只寫做法,不寫證明)
利用五邊形數可以得到
設\(n\)的分拆數是\(P_{n}\)
五邊形數對於一個相同的\(k\)有兩個,分別是\(\frac{k(3k - 1)}{2}\)和\(\frac{k(3k + 1)}{2}\),前面的系數是\((-1)^{k}\)
\(P_{n} - P_{n - 1} - P_{n - 2} + P_{n - 5} + P_{n - 7}.... = 0\)
然后寫成生成函數
\(P(x) - xP(x) - x^{2}P(x) + x^{5}P(x) +x^{7}P(x)... = x^{0}\)
然后兩邊都除上一個\(P(x)\)
得到
\(1 - x - x ^{2} + x^{5} + x^{7}... = \frac{1}{P(x)}\)
於是就變成了一個多項式求逆了
7.12 upd end
Day23 6.29
群論
置換類似矩陣,也是可以快速冪的
burnside引理
對於一個置換f,若一個染色方案\(s\)經過置換后不變,則\(s\)是\(f\)的不動點,設\(f\)的不動點是\(C(f)\),置換總數為\(s\),則答案是\(\frac{C(f)}{s}\)
Polya定理
不動點的顏色就是一個置換中染的顏色相同的方案數
如果有\(m\)種顏色,有\(k\)個圈,則一個置換的不動點是\(m^{k}\)
特征方程
如果有一個線性齊次遞推式(通常不會太長)
拿斐波那契數列舉例吧
\(F_{i} = F_{i - 1} + F_{i - 2}\)
列一個方程
\(x^{2} = x + 1\)
然后解出來兩個根是
\(\frac{1 + \sqrt{5}}{2}\)和\(\frac{1 - \sqrt{5}}{2}\)
然后代入\(F_{1} = 1\),\(F_2 = 1\)可以得到
\(A = \frac{\sqrt{5}}{5},B = -\frac{\sqrt{5}}{5}\)
於是通項就變成了
\(f_{n} = \frac{\sqrt{5}}{5}[(\frac{1 + \sqrt{5}}{2})^{n} - (\frac{1 - \sqrt{5}}{2})^n]\)
Berlekamp-Massey算法
求一個數列的最短線性遞推式
當然可能一不小心就求出遞推式了,然后就愉快矩陣乘法orn^2多項式取模orO(nlogn)多項式取模了。。。
還是數列1 2 4 9 20 40 90吧
我們一開始遞推序列是一個空序列
記為$R_{0} = {} $,序列的標號到\(0\)
delta是利用真實值和遞推數列算出來的值的差值
\(i = 1\)時,delta = 1,\(R_{0}\) 在 \(1\) 的時候出錯,所以 \(fail[0] = 1\) ,由於 \(a_{1}\)是一個非0元素,我們就填上一個0到序列里,所以\(R_{1} = \{0\}\)
\(i = 2\)時,delta = 2,於是我們找到\(cnt - 1\)也就是\(R_0\)第一個失敗的地方,1,我們想利用這個1構造一個遞推數列使得前\(i - 1\)個數都是0,而第\(i\)個數是1,然后\(R_{1}\)加上delta倍的這個遞推數列就可以了
具體來說這次操作可以歸納成這樣,若\(R_{cnt}\)到了\(i\),則記錄一個\(fail[cnt] = i\)不合法,然后找到\(fail[cnt - 1]\)的位置,構造一個新的遞推序列,把前\(i - fail[cnt - 1] - 1\)個位置都填成0,然后后面加上\(1\),再加上取反的\(-R_{cnt- 1}\)這個數列,這樣可以使得第\(i\)個位置有值是\(delta_{fail[cnt - 1]}\),剩下都是0,這個時候就是再乘上一個常數可以得到我們需要的那個序列
(也相當於我把\(R_{cnt - 1}\)的答案通過加0平移了一下……)
接着上面的例子說,這個時候\(fail[1] = 2\),然后構造遞推數列加了0個0,然后加上一個1,再加上取反的\(-R_{0}\),再乘上常數,可以得到\(R_{2} = \{2\}\)
\(i = 3\),遞推數列成立,繼續
\(i = 4\),\(fail[2] = 4\),\(delta = 1\),然后構造的數列是\(\{0,1,0\}\),乘上的常數是\(\frac{1}{2}\),於是我們可以得到新的數列是\(R_{3} = \{2,\frac{1}{2},0\}\)
\(i = 5\),仍然成立
\(i = 6\),\(delta = -\frac{9}{2}\),\(fail[3] = 6\),構造的數列是\(\{0,1,-2 \}\),乘上的常數是\(-\frac{9}{2}\)
然后可以得到\(R_{4} = \{2,-4,9\}\)
\(i = 7\),\(delta = 9\),\(fail[4] = 7\),構造的數列是\(\{1,-2,-\frac{1}{2},0\}\),乘上的常數是\(-2\)
然后得到數列\(R_{5} = \{0,0,10,0\}\)
Day 24 6.30
又是今天補昨天的,不過是最后一天了
Miller_Rabin
根據質數的兩個必要條件
$a^{p - 1} \equiv 1 \pmod p $
\(a^{2} \equiv 1 \pmod p\),\(a = 1\)或\(a = -1\)
這兩個條件是必要條件
我們隨機幾個比p小的質數做a,大概100以內選7個,判錯的概率很小
判的方法是把\(p - 1\)分解成\(2^{k} * s\),的形式,這個時候我們計算\(a^{s}\),然后把\(a\)不斷翻倍,看最后會不會得到1,若得到1看上一次的是不是\(-1\),否則p一定不是質數
Pollard_rho
我們在分解一個合數,他的最小質因子必然小於等於\(\sqrt{n}\),根據生日悖論,隨機一個數然后和這個數取gcd能找到這個合數的質因子的隨機次數是\(n^{1/4}\)(事實上,應該設最小質因子是\(p\),復雜度為\(\sqrt{p}\))
我們用一個偽隨機數,\(f(x) = x^{2} + c\),不過這個函數無法處理含有2的質因子,因為它在模4意義下只會生成\(a + 1\)和\(a\),所以我們只需要暴力去掉2就好了
這個\(c\)可以在每篩一次的時候遞增,初始值隨機,\(a = b = rand()\),然后\(a\)走一步,\(a = f(a)\),\(b\)走兩步\(b = f(f(b))\),由於這個函數的特殊性質,它的循環節期望是$O(\sqrt{p}) $
在篩的時候注意判n是質數的情況,這個可以用miller_rabin
模擬退火
大概剩下的都歸於奇怪的亂搞
我只記錄一下模擬退火如何以一定概率接受不優的解
設溫度為\(T\),錯解是\(tmp\),最優的是\(ans\)
那么可以用指數函數來計算這個概率\(exp((tmp - ans) / T)\),其中我們需要\(tmp - ans\)是一個負值,顯然這個負值越大,概率越大,且可以認為溫度越大,取錯解的概率越大
這樣我們就可以自欺欺人了!
(24天的一輪復習完結了)
(我到底復習了啥啊qwq)