2019清北学堂学习笔记
暴力求解法
迭代加深搜
適用于搜索樹深度不確定的時候,可以使用迭代加深搜。
步驟:
1.枚舉maxd表示最深枚舉深度; 2.假設當前深度為g(n),樂觀估計至少要h(n)層才能到達葉子節點,那么g(n)+h(n)>maxd時,就應該剪枝。在我理解看來,樂觀估計的意思是說不去管所有的限制,然后去計算當前點到終點的距離或者所需要的操作數量,即還需要h(n)層,這時候的h(n)才是最好的,即最小(大)的,可以利用來剪枝。A*就是把狀態樂觀估計還要h(n)層才到達的想法使用到bfs上面。
雙向搜索
對普通搜索的改進,使用的是BFS,大體流程就是從起點開始向終點搜,終點開始向起點搜,然后就直到第一個搜索碰到第二個或者是第二個碰到了第一個,然后答案就是兩個搜索步數的和再減去1。比較典型的例題有魔板,然而那個題目一共3個變換方式,還可以不用雙向搜索。
分治算法
數列上的分治
顧名思義,數列上的分治就是日常用的分治,最經典的例題就是求逆序對數量。使用的算法是歸并排序,比較兩個指針所對應的數字,如果左邊的大于右邊的,那么就說明,當前左邊的指針到mid的所有數字都是大于右邊指針所指的數字,那么答案就應該加上mid-l+1。
CDQ分治
可以代替數據結構的利器,可以解決的問題是區間加和單點修改。
\(\color{red}{流程:}\)
1. 按所有的按所有的操作等分分成前后兩部分
2. 處理前面部分的、后面部分各自的修改和查詢
3. 處理前面部分修改對后面部分查詢的貢獻(動態查詢變成靜態查詢)
聽老師說很好打,而且用處也很大,這個CDQ分治處理的問題需要滿足離線操作和修改與修改之間互不影響。因為是把動態詢問變成了靜態詢問,所以還可以通過分治把某些排序可以做好,更好地進行靜態查詢。
樹分治
樹的重心是指以該點為根時,最大子樹節點數最小。子樹節點數都<=n/2,層數也是有logn層,每一層都可以在O(n)的時間內處理問題。
貪心算法題目選講
貪心,我的理解就是就當前狀態來看,所選取的最優決策,但是不一定會是全局最優決策,所以很多題目看似是貪心,但是貪心并不正確,只能看直覺,畢竟證明很麻煩。典型例題是鋪設道路(積木大賽),就是在當前點時,對比上一個點,如果比上一點大,那么加上多出的一部分,反之,就是continue,然后更新last為當前點的大小,last初值為0.
簡單數學
歐幾里得算法
可以求出變量a,b的最大公約數。
\(gcd\)代碼:
int gcd(int a, int b){ return b == 0? a : gcd(b, a % b); }擴展歐幾里得算法
可以求解類似于\(ax+by=c\)的不定方程,有整數解的條件是\(gcd(a,b)|c\),即是在說\(gcd(a,b)\)必須是\(c\)的因子。
\(exgcd\)代碼:
void gcd(int a, int b, int &g, int &x, int &y){ if (!b) {g = a; x = 1; y = 0;} else{gcd(b, a % b, d, y, x); y -= x * (a / b);} }式子推導如下:
\(ax+by=gcd(a,b)\)
\(b \times x+(a- a/b \times b) \times y=gcd\)
\(y \times a+(x-y\times a/b)\times b = gcd\)
線性篩素數
學習過三種篩法,第一種,也是最耗費時間的就是,從2開始向n-1枚舉,對其稍加優化就是枚舉到 ,但是根本不會滿足需求。于是就有了埃式篩,時間復雜度可以做到O(nlognlogn),可以說是很接近線性了,但是又不是真正的線性篩,所以還需要優化。歐拉篩成功的做到了線性篩的要求,在O(n)的時間內篩出素數。
埃式篩代碼:
for(int i=2;i<=t;i++) if(prime[i]) for(int j=2 * i;j<MAXN , j += i)prime[j]=false;歐拉篩代碼:
for(int i=2; i<=n; i++){if(!vis[i]) prime[cnt++]=i; for(int j = 0; j < cnt && i * prime[j] <= n; j++){ vis[i * prime[j]]=prime[j]; if(i % prime[j] == 0) break;} }逆元
我理解的逆元就是一個數在模p的意義下的倒數?,F在也有了多種求解逆元的方法,比如費馬小定理,遞推式求逆元,擴展歐幾里得求逆元,當然用的最多的莫過于遞推式,因為很快的就能求出。
組合數
有一個公式 ,然后是就是圍繞著它進行推導,比如 等等。
數據結構
堆
堆有兩種,一個是大根堆,另一個是小根堆;一個是less,另一個是greater。
頭文件是
支持的操作有:
插入,時間復雜度為O(logn);
查詢,時間復雜度為O(1);
刪除,時間復雜度為O(logn).
如果卡常數的話可以使用make_heap之類的。
二叉搜索樹
樹高期望 \(logn\)
操作:插入,刪除,查詢
例如STL中的set,map
線段樹
線段樹可以維護區間信息的數據結構,每一個節點都對應著一個序列的區間。
可以支持單點修改或者是查詢。
動態開點
合并 復雜度就是兩顆線段樹重合線段樹重合節點個數
tree *merge(int l, int r, tree *A, tree *B){ if(A == NULL) return B; if(B == NULL) return A; if(l == r) return new tree(NULL, NULL, A -> data + B -> data); int mid = (l + r) >> 1; return new tree(merge(l, mid, A -> ls, B -> ls),merge(mid + 1, r, A -> rs, B -> rs), A -> data + B -> data); }樹狀數組
一個十分好用的數據結構,其重點就是lowbit函數。只能維護滿足可減性的量,例如和,異或和,而與,或不能用樹狀數組維護。線段樹包括了樹狀數組,但是樹狀數組常數小。
二維樹狀數組
與一維樹狀數組類似,只是多了一層循環。令s[x][y]表示x-lowbit(x)+1<=i<=x, y-lowbit(y)+1<=j<=y的a[i][j]的和
并查集
按秩合并+路徑壓縮可以證明復雜度是對的,但是也可以只寫路徑壓縮,是卡不掉的。
例題就是洛谷P1525關押罪犯。第一個做法是二分+二分圖匹配,第二個做法是并查集。
Trie
代碼:
//trie樹,字母集 int rt=1,cnt=1; int ch[N][26]; void insert(strint s) {int o=rt; for(re int i = 0 ; i <s.size;++i) { int k=s[i]-’a’; if(ch[o][k]) o=ch[o][k]; else ++cnt,o=ch[o][k]=cnt; } sz[o]++; } int query(string s) { int o=rt; for(re int i = 0 ; i <s.size ; ++ i ) { int k=s[i]-’a’; if(ch[o][k]) o=ch[o][k]; else return 0; } return size[o]; }Trie樹還是蠻重要的,數也可以上trie樹,但是需要變成二進制,這樣的話在一個序列中尋求兩個數的異或最大值就變得很容易求出來。比如說是異或最大值,我們讓每一個數字都拆分為二進制,然后上trie樹,總是選取1,如果沒有1再去選取0,這樣最后出來的數字就是最大的異或值。原理就是1000,總是會比0100好,更加優,所以一直選取1,沒有1再取0.
Hash
19260817是一個常用的mod數,素數。
Hash碰撞
本來不相同的兩個數可以在某一個數的時候會相同,被稱為hash碰撞。為了避免可以使用map映射或者雙模數。還有一個方法就是自然溢出,自然溢出加上雙hash還會更好,不容易被卡。
RMQ問題
對于長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在[i,j]里的最小(大)值,也就是說,RMQ問題是指求區間最值的問題。
St表
令f[i][j]表示從i開始,2^j個數的最小值
f[i][j]總共有nlogn個狀態
可以由j從小到大遞推求出
靜態查詢最好使用ST表,這樣的復雜度是最優的。
遞推式為
求LCA
1.倍增求LCA
\(\color{red}{步驟:}\)
1.先使兩個結點跳到同一深度。 2.如果兩個結點相等了,那直接返回這個結點 3.否則兩個結點一起跳到LCA的子結點位置 4.返回這個位置的父親,即為LCA2. 轉換為RMQ問題
\(\color{red}{步驟:}\)
1.求出樹的dfs序(每個結點進入和每次回溯到的時候都記錄) 2.記錄每個點第一次進入時在dfs序列中的位置pos[u] 3.兩個點u,v的LCA即為dfs序中[pos[u],pos[v]](不妨設pos[u]<=pos[v])中dep最小的結點3. Tarjan求LCA
\(\color{red}{步驟:}\)
1.記錄每個點的詢問 2.dfs回溯時用并查集合并(將子結點集合并到父節點集合,以父節點為根) 3.查詢lca(u,v)時假設此時u已訪問,v剛訪問到,那么u的并查集的根即為答案最小生成樹
Kruskal
貪心方法:將邊按照邊權排序,每一次都只是拿取邊權最小的邊,看它連接的兩個點是否在一個連通塊中,這里會用到并查集來維護,如果已經聯通了,那么就說明會有更小的邊已經連了;反之就加入,并且把兩個點聯通起來。
最短路徑
Dijkstra算法
支持單源最短路,可以使用堆優化,復雜度為O(mlogm),但是不可以去判負環。
SPFA算法
該算法可以判負環,但是日常還是不要用,因為復雜度是錯誤的。
判負環的方法:
1.用len[u]表示源點到u的最短路中有幾條邊。 2.在更新最短路長度的時候順便求出。 3.顯然如果沒有負環,len [u]<n 4.每一次頂點入隊時都要進行check,如果len[u]>=n,則負環存在Floyed算法
適用于多源最短路,直接三重循環枚舉,利用中轉站來使得兩個點的距離變小。由于比較暴力而且復雜度是O(n^3)所以一般也不會用到。但是該算法也是一種思想,也會用到其他的題目。
建圖技巧
通過添加虛擬點等手段將問題轉化,從而達到減少邊數等目的。
拓撲排序
給定一張有向圖,要求輸出一個序列,輸出是要滿足:如果圖中u到v有一張有一條有向邊,那么序列v在u的后面。如果該序列存在的話,那么就說明一定沒有環,因為如果有環的話就不會有入度為0的邊。
連通分量
在圖中:頂點之間有路徑一定互相到達。可以用并查集維護一個連通分量。
可以使用Tarjan算法在O(n+m)的時間內求出。
Tarjan算法
記錄兩個數組,一個表示沒個點被訪問的時間,另一個記錄搜索樹子樹的點能訪問的點DFN的最小值。
縮點
在圖中,由于有強連通分量,并且在強連通分量中的點也都可以很容易的到達,所以我們可以把強連通分量看作為一個點,這樣會比較好處理。
差分約束
直觀的看,差分約束就是幾個不等式,通過不斷的相加來獲得一個最終的形如x+y<=a的形式,a是一個常數,來獲得最大值。我們想要求的最大值,可以通過圖論跑最短路來求的。實際上就是x到y的最短路。
\(\color{red}{步驟:}\)
1.將形式轉化為xi-xj<=ci的形式
2.對于每一個不等式都是從xj向xi來那一條長為ci的邊
3.求出s到t的最短路
如果圖中有負環,那么就不存在解;
S到t沒有約束,即不能到達,所以是無限大。
環套樹/基環樹
其實就是樹上加上了一條邊。
步驟:
1.隨意找一個點開始當成樹進行dfs,并記錄每個結點訪問的時間戳dfn
2.dfs的過程中一定會有一個點往dfn比自己小的點連了邊,那么這條邊可以看成加上的那條。記錄下這條邊(u,v)
3.暴力讓u和v往上爬到根,記錄他們分別經過的點。
4.深度最大的他們都經過的點為他們的lca,u->lca之間的所有點+v->lca之間的所有點即構成環。
歐拉圖
通過圖中所有邊一次且僅一次行遍所有頂點的通路稱為歐拉通路。
通過圖中所有邊一次且僅一次行遍所有頂點的回路稱為歐拉回路。
具有歐拉回路的圖稱為歐拉圖。
具有歐拉通路的圖稱為半歐拉圖。
圈套圈算法
dfs搜索,不能再往下走(不能重復使用一條邊,但可以重復經過一個點)便回溯,回溯時記錄路徑,回溯時不清除對邊的標記,最后求出來的路徑就是歐拉回路。
DP
狀態與記憶化搜索
狀態的本質就是問題的子問題。不論是搜索還是DP都需要設計問題的狀態,設計出了狀態,就可以找出狀態與狀態之間的關系,然后再進行推導,狀態之間的轉移就是狀態之間的聯系,一般地DP是遞推式的形式。狀態需要保證無后效性和最優子結構。
動態規劃和記憶化搜索都做到了每一個狀態只會計算一遍,去掉了冗余計算。記憶化搜索也是搜索的一個有效的剪枝方法,復雜度多半是多項式級別的。記憶化搜索的代碼比DP的代碼更好理解,代碼實現也容易。
線性DP
最基礎的DP就是線性DP了吧,一般的話都會是一個for循環求出答案,復雜度一般都是O(n)左右的。學習好線性DP有助于對DP的理解,還是蠻重要的。
多維DP
樹形動態規劃
樹形動態規劃是在樹上,根據樹的拓撲性進行動態規劃。我們依舊可以使用原來的思路來解決。
背包動態規劃
背包DP主要包含三種問題,01背包,完全背包,多重背包。01背包是每一個物品只能選擇一件,并且是倒著枚舉;而完全背包是可以選擇無限件,是正著枚舉的;多重背包是每一個物品有多件,可以隨便選擇多件。
P4394 選舉
按照席位數量從大到小排序,然后再去維護一個01背包。因為題目要滿足任意政黨退出時,其他政黨的席位不得超過一半,所以需要枚舉j>sum/2,j-a[i]<=sum/2。
數位DP
數位DP可以解決區間內求滿足一定條件的數的個數。我們這里運用了前綴和的思想,在詢問[l,r]這個區間時,如果直接枚舉是會超時的,所以我們可以用[0,r]-[0,l-1]來獲得[l,r]區間的滿足條件的數的個數。
狀壓DP
狀壓DP,在題目中,我們定義某些狀態是很繁瑣的,所以需要進行簡單化,我們可以用幾個簡單的數字來進行表示狀態,最形象的莫過于二進制數字表示狀態。例如在八數碼這個題中,我們可以使用到這個思想,來使得狀態好表示,本來是一個3*3的矩陣,我們可以用字符串來表示狀態,來進行變換。
DP的優化
關于DP的優化,我們最常使用的就是單調隊列優化,單調隊列可以支持插入一個元素和查詢當前隊列中的最大值。單調隊列優化應用的DP方程多半是f[i]=max(f[l]...f[r])+a[i].我們就可以預處理出f[l]~f[r]中的最大值。
記錄:n(1+1/2+1/3+...+1/n)=ln(n)
轉載于:https://www.cnblogs.com/handsomegodzilla/p/11605756.html
總結
以上是生活随笔為你收集整理的2019清北学堂学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日记-致我那易逝的时光
- 下一篇: vim中文编码问题