若是要用一個詞來形容上午的測試,那真是體無完膚。
成績:node
題目 | 成績 | 評價 |
---|---|---|
T1 | 50 | 通常 |
T2 | 10 | 大失所望 |
T3 | 0 | 差 |
:指經過觀察、概括,發現較大規模問題和較小規模問題之間的關係,用一些數學公式表達出來,在一些教材中,也稱爲計數DP。遞推的模型最主要有:斐波那契數列,卡特蘭數,bell數,錯排等等。c++
:所謂遞歸,是指函數「本身調用本身」的一種編程方法,在解決一個問題時,若是發現問題能拆解爲一個或多個相同規模的子問題時,就能夠考慮使用這種方法求解。最經典的題型有漢諾塔等。算法
:分治解決問題的通常步驟:
分,將問題劃分爲若干個規模較小、形式相同的子問題
治,遞歸求解,當問題規模足夠小時直接求答案
合,將規模更小的問題獲得的答案合併起來,獲得原問題的答案。
主要模型應用有歸併排序、快速冪等。
是否是很難懂?吶看一個例題
分治經典例題:給定 2^n * 2^n 的棋盤,其中有一個壞點,須要用若干個 L 型方塊覆蓋棋盤:不能覆蓋到壞點,其餘點要被覆蓋剛好一次。給出方案。
解:這道題目有個基本單元必定反覆存在:
這個只須要放一個L形狀的方塊去覆蓋就解決了問題。咱們把它拓展一下,把每一個格子想成多個格子的集合,若是咱們能肯定這個壞點在其中一個集合裏面,那麼咱們能夠把整個問題分紅四個部分:
而後發現左下角的格子就是一個基本模型,而後中間的四塊連在一塊兒也是一個模型,而後咱們能夠在中間搞事情,放上一個L形狀的木塊,因爲每一個格子只能剛好被覆蓋一次,因此咱們能夠把全部被覆蓋的地方都標記爲壞點。而後另外三個大格子也變成了三個模型
是否是就作出來了。
二分:二分實際上是分治的一種。二分是競賽中一個很是實用的算法思想經過二分,把最優化問題轉化爲斷定性問題,把優化目標轉化爲斷定的標準,就能發現問題的一些「貪心性質」,從而設計高效算法進行斷定。
求「 XXX 的最大值 」 最小多是多少(或者反過來)均可以考慮二分
有些時候,二分的模型比較隱蔽,須要更深刻的思考和發現性質
二分答案的時間複雜度是O(logn)編程
:倍增字面上意思是:成倍地增長。當咱們模擬一個過程時,一步一步進行太慢,考慮把模擬的步數二進制分解;通過一些預處理,每次能夠模擬 2^i 步,從而達到優化複雜度的目的。主要模型有RMQ,LCA等。數組
:哈哈哈哈哈哈貪心是個好東西啊。由貪心獲得局部最優解,從而獲得全局最優解,通常容易實現、時間複雜度優秀,可是……難於證實,貪心的腦洞通常都很大。數據結構
特特特特特特就是個數論。ide
模算術能夠這樣簡單理解:就是不停地模啊模。就能夠了。好比a+b=c(mod p)就表明(a+b) mod p=c mod p;模算術只有在模意義下才有固定的意義。函數
類比倒數,若是a*b=1,那麼ab互爲倒數;相似地,咱們定義若是在 mod p意義下,a *b=1,那麼a和b互爲mod p意義下的乘法逆元。工具
方程 ax + by = c 有解,當且僅當 gcd(a, b) | c。性能
gcd(a, b) = gcd(a - b, b)
事實上,定理直接給出了一種計算 gcd 的方式,直接根據上述公式計算,複雜度 O(\log min(a, b))
事實上,咱們能夠直接在歐幾里德算法求解 gcd(a, b) 的過程當中,構造一組 ax + by = gcd(a, b) 的解
這個方法依賴於遞歸的思想
邊界:b = 0 時, a * 1 + b * 0 = gcd(a, b)
設咱們找到了一組 bx + (a % b)y = gcd(a, b) 的解,那麼:
bx + (a - [a / b] * b]) y = gcd(a, b) ==>
ay + b * (x - [a / b] * y) = gcd(a, b)
令 x’ = y, y’ = (x - [a / b] * y),能夠獲得:
ax’ + by’ = gcd(a, b)
令 d = gcd(a, b)
當咱們求出 ax + by = c 的一組解 x0、y0 以後
方程的通解具備如下形式:
x = x0 + k * (b / d)
y = y0 - k * (a / d)
直觀理解一下,這是一個設法讓正負互相抵消的過程
定義:大於 1 的、只被 1 和它自己整除的正整數稱爲素數
惟一分解定理,
又稱算術基本定理:
對於正整數 n,咱們必定能夠將其寫爲若干個質數的冪的乘積形式,即
n = ∏ (pi ^ ai),其中 pi 爲質數
而且,這種分解是惟一的
若是咱們要篩出 [1, n] 內的全部素數,使用 [1, √n] 內的素數去篩就能夠了
設數組 mark[],mark[i] 表示 i 是否被某個素數篩過
從 2 開始枚舉每一個數 i:
若 mark[i] = 0,表示 i 沒有更小的素因子,從而知道 i 是素數。枚舉 i 的全部倍數 j,令 mark[j] = 1
若 mark[i] = 1,知道 i 是一個合數
複雜度 O(n\lg\lg n)
來一段程序吧:
埃氏篩法的應用:在進行篩法的同時,咱們能夠對 [1, n] 內的合數進行素數分解,從而完成一些其餘工做,,例如,對於一個素數 i,枚舉它的倍數 j 時,把 j 中的全部因子 i 除乾淨,就知道 j 的質因子分解中 i 的冪了。有一類問題,須要你預處理區間 [L, R] 內的素數分佈,其中 R - L <= 10^6, R <= 10^10。此時,區間 [L, R] 的長度比較小;另外一方面,√R <= 10^5。咱們就能夠考慮使用埃氏篩的思想來求解問題。用一個數組 mark2[] 維護區間 [L, R] 被篩的結果,用素數 p 去篩 [L, R] 的計算。爲 (R - L) / p,總計算複雜度大概爲 H_{R-L},即 O((R - L) \log R)
p 是質數的充要條件爲
(p - 1)! ≡ -1 (mod p)
充分性:
若 p 不是質數,則 gcd( (p-1)!, p ) > 1,與 Bezout 定理相悖
必要性:
考慮 [1, p) 中的某個數 x 和它的乘法逆元 y,若是 x != y, 那麼 xy ≡ 1,能夠令它們互相抵消,因而只須要考慮 x 是本身的逆元的狀況,解 x^2 ≡ 1 ==> p | (x - 1) * (x + 1) ,由於 p 是質數,只有多是 x ≡ ±1,得證
歐拉函數 φ(n),表示不大於 n 的、與 n 互質的正整數個數,令 n = ∏ (pi^ai),則 φ(n) = n * ∏ (1 - 1/pi)
計算方法:
對 n 進行質因子分解的過程當中維護,O(√n)
埃氏篩的時候順便維護,複雜度同埃氏篩
歐拉函數的兩個公式:
∑ { φ(d) | d | n } = n
證實:在 [1, n] 中,剛好有 φ(d) 個數和 n 的 gcd 爲 n / d
n > 1 時, ∑ { d | gcd(d, n) = 1 } = n * φ(n) / 2
證實:當 gcd(x, n) = 1 時,可得 gcd(n - x, n) = 1,能夠將與 n 互質的數兩兩配對
對於 n <= 2 的狀況,特殊討論
剩餘類:
給定 n,整數按照模 n 取值的不一樣,能夠分爲 n 個子集,稱爲剩餘類
剩餘系:
給定 n,從模 n 的 n 個剩餘類中各取一個數構成的集合,稱爲模 n 的一個剩餘系,剩餘系通常指徹底剩餘系
簡化剩餘系:
也稱既約剩餘系,是模 n 的徹底剩餘系的一個子集,其中每一個元素與n 互素,容易驗證,簡化剩餘系中剛好有 φ(n) 個元素
考慮模 n 的一個簡化剩餘系 S
由於 S 中的每一個元素和 n 互質,它們在模 n 意義下存在乘法逆元
任取一個 a,使得 (a, n) = 1
考慮集合 T = { ax | x ∈ S }
容易驗證:|T| = |S|,且 |T| 中的數模 n 互不一樣餘,|T| 中的數均和 n 互質
由此,可得 T 也是模 n 的一個簡化剩餘系。由於 S 和 T 均爲模 n 的簡系,可得:
∏ {x | x ∈ S} ≡ ∏ {y | y ∈ T} ==>
∏ {x | x ∈ S} ≡ ∏ {ax | x ∈ S} ==>
∏ {x | x ∈ S} ≡ a ^ {|S|} * ∏ {x | x ∈ S} ==>
a^{|S|} ≡ 1
這就是歐拉定理:
若 (a, n) = 1,則 a ^ φ(n)=1(模意義下)
由歐拉定理能夠推導出一些頗有用的結論
費馬小定理:
若 p 是質數且 (a, p) = 1,則 a ^ (p-1) ≡ 1 (mod p)
一個應用:a ^ (p - 2) ≡ 1 / a (mod p),能夠經過快速冪求逆元
歐拉定理 EXT:
若 (a, p) = 1,則 a ^ x ≡ a ^ (x % φ(p)) (mod p)
任何狀況下,若 x > φ(p),則 a ^ x ≡ a ^ (x % φ(p) + φ(p)) (mod p)
能夠用來求解這樣的同餘方程組:給定長度爲 k 的數組 a[] 和數組 m[],求解,x ≡ a[i] (mod m[i]),保證 m[i] 兩兩互質。
推導:定理:令 M = ∏ (m[i]),方程在 [0, M) 中有惟一解 x0,而且通解具備 kM + x0 的形式
這裏證實惟一性:
設 x1 != x2 是方程的兩個解,咱們有
x1 - x2 ≡ 0 (mod m[i])
x1 - x2 是每一個 m[i] 的倍數,它們必然也是 M 的倍數,即 M | x1 - x2
因而,x1 和 x2 最多隻有一個落在區間 [0, M) 中
解的存在性,咱們經過一種構造算法來證實
。
構造解
令 Mi = M / m[i],由於 (M[i], m[i]) = 1,咱們能夠找到 Mi 模 m[i] 的乘法逆元 t[i]
考察 Mi * t[i]:
j = i 時,M[i] * t[i] ≡ 1 (mod m[j])
j != i 時,M[i] * t[i] ≡ 0 (mod m[j])
令 x = ∑ (a[i] * M[i] * t[i])
不難驗證,x 是知足要求的
模數不互質
模數不互質的時候,怎麼辦?
將方程兩兩合併,考慮方程組:
x ≡ a1 (mod m1)
x ≡ a2 (mod m2)
令 x = k1 * m1 + a1 = k2 * m2 + a2
則有 k1 * m1 - k2 * m2 = a2 - a1
由 Bezout 定理判斷解的存在性,用 extend_gcd 解方程便可
事實上,能夠獲得一個推論:
若是存在解,則 x 在 [0, lcm(m1, m2) ) 中有惟一解
應用
能夠直接解決一類問題,有些數學計算題,給出的模數 m 並非一個素數,而不少計算在模素數(或素數的冪)下會方便不少,因而能夠將 m 分解質數,求答案模每一個素數(的冪)的值,最後使用中國剩餘定理合併
C(n, m) ≡ C(n / p, m / p) * C(n % p, m % p) (mod p)
證實:
核心思想:用兩種方法展開 (x + 1) ^ n,考慮 x^m 一項的係數
重要公式:(x + 1) ^ p ≡ x^p + 1 (mod p)
一種優秀的篩法
線性複雜度的來源:每一個數只會被它的最小的質因子篩掉
簡單證實: 設有一個數 m = p * q * s
其中 p 是最小的質因子,q 是另一個質因子,若是 m 會被 q 篩掉,那必定是在 i = p * s 時篩掉的,可是咱們發現 i = p * s 時,j 枚舉到 p 這個質數時就會 break,把每一個數被篩的過程畫成一張圖,發現相同的質因子是在連續的幾步被篩掉的。實際上,篩的過程至關於按照質因子降序分解了每一個數。
如有函數 f(n) 的定義域爲正整數,值域爲複數,稱爲數論函數
進一步地,若數論函數 f(n) 知足:對於互質的 p、q,有 f(p * q) = f(p) * f(q),稱爲積性函數,或者說函數知足積性。更進一步地,若數論函數 f(n) 知足:對於任意 p、q,有 f(p * q) = f(p) * f(q),稱爲徹底積性函數
常見的積性函數
除數函數 σk(n)=∑(d|n) d ^ k,表示n的約數的k次冪和
約數個數函數 τ(n)=σ0(n)=∑(d|n) 1,表示n的約數個數,通常也寫爲d(n)。
約數和函數 σ(n)=σ1(n)=∑(d|n) d,表示n的約數之和
歐拉函數 φ(n)
莫比烏斯函數μ(n):
n 有平方因子時值爲 0
不然值爲 (-1) ^ (質因子個數)
元函數 e(n) = [n = 1],徹底積性
恆等函數 I(n) = 1,徹底積性
單位函數 id(n) = n,徹底積性
對兩個數論函數進行的運算
設咱們有兩個數論函數 f(n) 和 g(n)
它們的狄利克雷卷積是一個新的函數 (f * g) (n)
設這個函數爲 h
咱們有 h(n) = ∑(k|n) f(k) * g(n / k)
Dirichlet卷積的性質
積性函數的狄利克雷卷積仍然知足積性
證實:對互質的 p、q,有
h(p) * h(q)
= ∑ { f(a) * g(b) | ab = p }* ∑ { f(c) * g(d) | cd = q }
= ∑(ab = p, cd = q) f(ac) * g(bd)
=∑(xy = pq) f(x) * g(y)
=h(p * q)
注意:徹底積性函數的狄利克雷卷積不必定知足徹底積性。
狄利克雷卷積知足結合律,即對於三個數論函數 f、g、h,有 (f * g) * h = f * (g * h)
證實:
(f * g) * h (n)
= ∑(ab = n) h(b) * (∑(xy = a) f(x) * g(y))
= ∑(xyb = n) f(x) * g(y) * h(b)
= ∑(xy = n) f(x) * (∑(ab = y) g(a) * h(b))
= f * (g * h) (n)
Dirichlet 卷積同時也具備交換律、分配律
Dirichlet 卷積運算存在單位元(元函數 e):f * e = e * f = f。
常見的公式
id = �� * 1
d = 1 * 1
σ = id * 1
e = 1 * μ (反演式) *
�� = id * μ *
積性函數的預處理
藉助線性篩,能夠在 O(n) 時間內預處理出某種數論函數在 [1, n] 的取值
咱們以約數個數函數爲例說明
莫比烏斯函數、歐拉函數、約數和函數的預處理都應該熟練掌握
約數個數函數
n = ∏ (pi^ai)
d(n) = ∏ (ai + 1)
咱們維護一個輔助數組 num[x] ,表示 x 的最小的質因子的冪次
線性篩每次會篩掉一個數最小的質因子,這讓咱們能夠很方便地維護 num[],從而計算 d[]
咱們設置一個步長 m
則任意一個可行解能夠寫成 x = am + b (0 <= b < m)
A ^ (am + b) = B mod P => A ^ b = B * A ^ (-am) mod P
把 0 <= b < m 的 A ^ b 預先算出來,扔到哈希表中
查詢的時候暴力枚舉 a,算出等式右邊,在哈希表中查詢
複雜度 O(P / m) + O(m)
m = √P 時,取得最優複雜度 O(√P)
若是用 map 實現哈希表,還要乘一個 log
若是 P 不是質數,怎麼辦?
若 (A, P) = 1,算法仍是能夠跑的,求逆元改爲 extend_gcd 就好了
(A, P) != 1時,問題出在哪兒?
A 不存在關於 P 的逆元
令 d = gcd(A, P)
A/d * A ^ (x - 1) = B/d (mod (P/d) )
遞歸求解
最多 log(P) 層,注意最後算出來的答案 x 要加上層數
所以,須要特判掉 x <= log(P) 的狀況
黑色星期一??蒟蒻被虐的第一天。數論越講到後面越聽不懂,上面列出的全部知識點都要本身好好去學下。
題目 | 成績 | 評價 |
---|---|---|
T1 | 10 | 覺得暴力能拿60分,結果只有十分 |
T2 | 0 | 翻車 |
T3 | 4 | 騙到4分 |
翻車翻到底了
搜索算法是利用計算機的高性能來有目的的窮舉一個問題解空間的部分或全部的可能狀況,從而求出問題的解的一種方法。現階段通常有枚舉算法、深度優先搜索、廣度優先搜索、A* 算法、回溯算法、蒙特卡洛樹搜索、散列函數等算法。在大規模實驗環境中,一般經過在搜索前,根據條件下降搜索規模;根據問題的約束條件進行剪枝;利用搜索過程當中的中間解,避免重複計算這幾種方法進行優化。
dfs深度優先搜索和bfs廣(寬)度優先搜索是搜索的兩種基本模型。
例題1——鬥地主
題目地址
luogu鬥地主
luogu鬥地主加強版
解:咱們須要考慮預處理dp數組: dp[x][y][z][w] 表示分別有 x、y、z、w 種出現 四、三、二、1 次的牌,不出順子最少幾步打完。DFS 搜索打順子的狀況,結合 dp 計算答案。加上最優性剪枝就能夠A掉。
中途相遇法指的是,從起點和答案兩邊同時開始搜索,在中間相遇並求解的方法。這種方法有什麼好呢?咱們用給一張圖片來解決。
空白部分在中途相遇法中就能夠忽略掉了。
例題2
已知 N 元高次方程:sum_{i<=N} k[i] * x[i] ^ p[i] = 0,設未知數均爲不大於 M 的正整數,求解的個數。N <= 6, M <= 150。
解:將前半部分的搜索結果存入 Hash 表,再搜後半部分。這就是一箇中途相遇法。
給定素數 M 和整數 A、B,解方程 A^x = B (mod M),給出任意一組解,或者說明無解。A, B <= M <= 10^9。
解:這道題目須要使用離散對數。什麼是離散對數呢?
咱們能夠聯繫對數的概念,若A^x=B那麼,x是B以A爲底對數。一樣的,在模意義下,A^x=B(mod m),x就是B以A爲底,模m意義下的對數。
啓發式搜索又稱爲有信息搜索,它是利用問題擁有的啓發信息來引導搜索,達到減小搜索範圍、下降問題複雜度的目的,這種利用啓發信息的搜索過程稱爲啓發式搜索。啓發式搜索中應用最爲普遍的搜索技巧當屬 A* 和 IDA* 了,前者是 BFS 的啓發式版本,後者是前者的迭代加深版本。
原理
A* 和 IDA* 算法中,對每個狀態 x 引入了一個估價函數 f (x) = g(x) + h(x), 其中 g(x) 是目前狀態的實際代價,而 h(x) 是 「目前狀態到目標狀態的估計代價」。在 A* 算法中,估價函數的做用是調整搜索順序讓最優的解最 先搜索到。而在 IDA* 中,估價函數做爲最優性剪枝出現。 爲了保證正確性,通常要求 h(x) <= 從 x 到目標狀態的實際代價。特別地,當 h(x) = 0 時,A* 和 IDA* 分別退化爲通常的BFS和迭代加深算法。
算法原理:
A*:
BFS 的改進,用優先隊列維護全部能夠擴展的狀態
每次把指望裏目標更近的狀態拿出來擴展
IDA*:
從小到大「試答案」並進行斷定
ans = 0; while (!check(ans)) ans++;
斷定通常使用深搜(當 深度 > ans 就沒必要搜下去了)
優劣比較:
A*:容易理解,實現容易;空間需求巨大
IDA*:不太容易實現,空間需求小,可是有重複擴展問題
PS:由於通常搜索樹第 x 層大小遠大於 x-1 層,通常認爲 IDA* 的在找到答案以前的重複擴展都「微不足道。
例題3——騎士精神
題目地址
luogu騎士精神
在一個 5 * 5 的棋盤上有 12 個白騎士和12 個黑騎士,每次能夠選擇一個騎士合法地走到空地,給定初始和目標狀態,問最少走多少步能到達目標狀態,(同種顏色的騎士是不互相區分的)。
小總結
從名字就能夠看出,啓發式搜索是一個比較玄學的東西,具體效果和估價函數有很大的關係,若是能判斷一道題是玄學搜索,或者沒有其它好辦法的時候,不妨試一試。
一種強大的搜索算法,可是咱們並不講,有興趣的同窗自行查閱相關資料。
工具和方法
基礎計數原理——加法、乘法原理(ex:減法、除法原理)
排列組合數及其公式、容斥大法
ex:線性遞推數列、母函數方法
經典計數數列
Fibonacci、Catalan、Bell 數
錯位排列數
知足的狀態轉移方程是:f(n
)=f(n-1)+f(n-2),邊界條件是f(1)=1,f(2)=1;
例題4——數樓梯
題目地址:luogu數樓梯
解:咱們能夠考慮最後一級(第n級)們怎麼越過去呢,很顯然,咱們要麼同時越兩級上去,要麼只越一級上去。若是越兩級,那麼去掉這兩級後,剩下n-2級樓梯就是一個規模更小的子問題了;若是越一級,那麼去掉這一級後,剩下n-1級樓梯就是一個規模跟小的子問題了,根據加法原理,咱們最後n級樓梯的解就是兩個規模分別爲n-1和n-2的子問題的答案之和。f(n)=f(n-1)+f(n-2);就是一個斐波那契數列。
卡特蘭數應當知足下面的關係:
卡特蘭數知足遞推式:
h(n)= h(0)* h(n-1)+h(1)*h(n-2) + … + h(n-1) *h(0) (n>=2)
或者知足
h(n)=h(n-1)*(4 *n-2)/(n+1);
並且知足
h(n)=C(2n,n)/(n+1) (n=0,1,2,…)
也就是知足
h(n)=c(2n,n)-c(2n,n-1)n=0,1,2,…)
例題5——棧
題目地址
luogu棧
解:咱們這道題目須要枚舉的是每一元素出棧的順序,對於第一個點,若是它是第一個出棧的,那麼它以前就會有0個元素出棧,這個是一個規模爲0的子問題,記做f(0),而後,它以後會有n-1個元素出棧,這是一個規模爲n-1的子問題,記做f(n-1),根據乘法原理,當第一個點是第一個出棧的時候,方案數爲f(n-1)*f(0);一樣的,咱們繼續枚舉,若是第一個點是第二個出棧的,那麼它以前的點有1個,出棧的方案數是f(1),在它以後有n-2個點,出棧的方案數是f(n-2),根據乘法原理,f(n-2) *f(1)…………咱們發現這是一個卡特蘭數。
知足:s(n + 1, k) = s(n, k - 1) + n * s(n, k)
S(n,0) = 0,
S(1,1) = 1.
邊界條件: S(0 , 0) = 1 S(p , 0) = 0 p>=1 S(p , p) =1 p>=0
一些性質: S(p ,1) = 1 p>=1 S(p, 2) = 2^(p-1)– 1 p>=2
經典模型:包含n個元素的集合分做k個環排列的方法數目
知足:s(n + 1, k) = s(n, k - 1) + k * s(n, k)
S(n,k)=0; (n
Bn是基數爲n的集合劃分數目。集合S的一個劃分是定義爲S的兩兩不相交的非空子集的族,它們的並是S。
bell的遞推公式:
並且適合Dobinski
也是適合Touchard同餘:
若p是任意質數,則會有
每一個bell數都是第二類斯特林數的和
今天主要講了搜索入門和簡單計數。搜索入門中A*和IDA *要從新去學習一下,簡單計數中的一些公式要再記憶一下。
···字符串 Hash:一種從字符串到整數的映射
···經過這樣的映射,把比較兩字符串是否相同轉化爲兩整數是否相同
····若比較發現兩字符串hash值相等,咱們認爲兩字符串很大多是相同的
····另外一方面,若 hash 值不等,則兩字符串必定不一樣
····比較字符串 O(L),比較整數 O(1)
··競賽中經常使用的 hash 策略
··把字符串視爲一個 base 進制的大整數,對某個質數 P 取模獲得 hash 值
··sum_i = (sum_{i-1} *base + str_i) mod P
··base 能夠取 3一、13一、13131 等,須要知足 base > |字符集|
··P 取 long long 範圍內一個質數,注意溢出問題
··使用 unsigned long long 天然溢出能夠視爲對 2^64 取模
(電腦自動取最低的64位 即爲自動取模)
··可是可能被卡(對任意base)
··懼怕 Hash 被卡的同窗,也能夠選擇雙 hash(常數翻倍
····給定字符串 S,預處理出它的前綴 Hash 函數;同時計算好 mod P 意義下 base 的冪次表
····sum[i] = (sum[i - 1] * base + str[i]) % P
····pw[i] = (pw[i - 1] * base) % P
····基礎應用:
··提取一段子串的 hash 值
··合併兩個串的 hash 值
··O(\log n) 計算兩個子串的 lcp 和字典序大小
想提取【l,r】的哈希值:
【l,r】=s(r)-s(i-1)*10^(l+r-1)
eg:
企鵝QQ
給定 N 個長度均爲 L 的串
問有多少對字符串知足:剛好有一位對應不一樣
N <= 30000, L <= 200
解:枚舉刪掉每個位置,用 Hash 來進行答案統計
Trie 樹
又稱字母樹,能夠用來維護字符串集合
優化思想是,利用字符串的公共前綴來減小查詢時間,最大限度地減小無心義的比較
結構:有根樹,每條邊上存有一個字符
從根到每一個葉子的路徑上通過的字符寫下來,對應了一個字符串
無敵僞代碼:
支持的操做:插入、查找、刪除
例 1
給定 2 * N 個字符串,你須要將它們配對起來
兩個字符串 x、y 配對的得分是它們的 lcp 長度
最大化得分
N <= 10^5,字符串總長 <= 2 * 10^6
解:建出 trie 樹,兩個串的 LCP 即爲它們的 LCA 的深度
使用貪心算法,按照樹的 DFS 序列配對
(樹的DFS序列如圖)
例 2
給定一棵有根樹,每條邊有權值 w_i
求樹上的一條簡單路徑,使得路徑通過的邊權異或和最大
N <= 2 * 10^5, w_i <= 10^9
解:
記錄 dis[a] 表示 a 到根的鏈的異或和
考慮 x、y 之間的鏈的異或和,設 LCA 爲 z
= (dis[x] ^ dis[z]) ^ (dis[y] ^ dis[z]) = dis[x] ^ dis[y]
不難發現與 z 無關!
因而問題轉化爲,給定 N 個數,選出兩個使得異或和最大
解 cont’d:
考慮枚舉兩個數之一 x,咱們想在其它數中找到一個與 x 的異或和最大
從高到低考慮每一位,儘量讓更高位爲 1
不難發現可使用 Trie 樹!
複雜度 O(N * 32)
維護 N 個集合,初始時第 i 個集合爲 { i }
支持兩個操做:
把兩個集合合併起來
查詢兩個元素是否在同一集合
N <= 10^6
對每一個集合,創建一個有根樹的結構
令樹的根爲整個集合的「表明」
想知道兩個元素是否在同一集合,只需比較它們的表明
合併時,將一棵樹接到另外一棵下邊便可
優化策略
路徑壓縮
按秩合併(把小的併到大的裏面去)
能夠證實,使用這兩種優化的並查集複雜度爲 O(α(n))
絕大多數狀況這個值不大於 5,能夠認爲是線性的
應用
最小生成樹的 Kruskal 算法
Tarjan’s off-line LCA Algorithm
帶權:
在一些應用中,能夠在每一個點上額外維護一些信息,表示「它與父親」之間的關係
進而嘗試推算集合中任意兩個元素之間的關係
例:
某市有兩個幫派,有 N 我的,每一個人屬於兩個幫派之一。
給定 M 個事件:
1 x y,表示告訴你 x 和 y 屬於同一幫派
2 x y,表示告訴你 x 和 y 不屬於同一幫派
3 x y,表示請你推理 x 和 y 之間的關係
N <= 5 * 10^5, M <= 10^6
解:
給每一個人額外維護一個標記 rel[x] 表示 x 和 x 的父親的關係
由 rel[x] 和 rel[fa[x]] 能夠推算 x 和 fa[fa[x]] 的關係。。。以此類推能夠推算 x 和 Root[x] 的關係
因而任意兩我的只要在同一連通塊,就能推算他們的關係
問題:這個並查集如何使用路徑壓縮優化呢?
只按秩合併
只按秩合併的並查集,能夠在合併的時候必定程度上保留元素合併在一塊兒的 「過程」
看一個經典例題
給定 N 個點,支持 M 個操做:
1 x y,在 x 和 y 之間連邊
2 x y,詢問 x 和 y 是否連通,若是是,那它們最先在哪一次操做以後連通的
N <= 2 * 10^5, M <= 5 * 10^5
解:
@貨車運輸
離線的時候能夠建樹倍增 blabla。。。
強制在線呢?
只按秩合併,link(x, y, tim) 時,咱們在 Root[x] 和 Root[y] 之間連一條邊權爲 tim 的邊
詢問 (x, y) 時,找到 x 和 y 之間邊權最大的邊便可
這種算法的複雜度是容易證實 O(\log N) 的正確性?
支持這樣幾種操做的數據結構:
插入一個優先級爲 key 的元素
詢問優先級最高的元素
刪除優先級最高的 / 任意一個元素
升高一個元素的優先級值
優先隊列通常使用堆來實現
最經典的堆即爲大名鼎鼎的二叉堆
二叉堆是一個徹底二叉樹結構,而且它具備堆性質:
每一個點的優先級高於它的兩個孩子(若是有)
能夠用一個數字來存儲二叉堆,避免指針:
1 是根結點
對於 x,它的左右孩子分別是 2x 和 2x+1
容易驗證 N 個點的二叉堆,它用到的數組即爲 1 ~ N
給定一個大小爲 N 的數組,咱們能夠 O(N) 的建堆(How?
隨着操做的進行,二叉堆的「堆性質」可能會遭到破壞,爲此咱們定義兩種調整操做,來維護二叉堆的堆性質保持不變
向上調整:
當一個點的優先級升高時,咱們須要向上調整
比較它和它的父親的優先級,它的優先級高就與父親交換位置並遞歸進行
向下調整:
當一個點的優先級下降時,咱們須要向下調整
比較它和它左右兒子中優先級較高的那個,它的優先級低就與兒子交換並遞歸下去
容易驗證兩種操做的複雜度均爲 O(\log N)
插入:插入一個葉子,而後向上調整
詢問:返回 a[1]
刪除根:令 a[1] = a[N],而後向下調整
也是一種優秀的堆
而且是支持合併的(可並堆)
比較簡單,容易實現
可是咱們不講
Dijkstra 算法和 Prim 算法的優化
哈夫曼編碼
一些奇怪的應用
給定數軸上 N 個點,你須要選出 2 * K 個,把它們配對起來
把兩個點配對起來的花費是它們座標之差的絕對值
最小化花費
N <= 3 * 10^5
解:
必定是取相鄰兩點配對
問題能夠轉化爲,選出 K 個相鄰點對
進一步轉化爲,有 N - 1 個線段,選出 K 個,且相鄰的不一樣時選
用堆進行貪心
給貪心一個「修正」的餘地:
選了 p[i],把 p[i - 1] + p[i + 1] - p[i] 入堆
炒股,一共有 2 * N 天,天天有一個買入和賣出價
剛開始時你有 0 支股和充分多的錢
天天要選擇買或者賣一支股票(股票數時刻不爲負
最後一天結束你要清倉
雖然你有充分多的錢,你仍是想知道本身最多能在這些天賺到多少錢
N <= 2 * 10^5
解:轉化成括號序列問題?
線段樹是一種二叉搜索樹,通常能夠用來維護序列的子區間
對一個長度爲 n 的序列建線段樹,根結點即表示 [1, n]
對於一個表示 [l, r] 的節點:
若 l = r,則它是葉子
不然,令 m = (l + r) / 2,它有左右兩個孩子,分別記爲:[l, m] 和 [m + 1, r]
不難驗證,這樣一個線段樹中有 2N - 1 個節點,而且樹的深度是 O(\log N) 級別
線段樹的優化思想:
根據問題的要求,用每一個節點維護它對應的子區間中、能夠高效合併的相關信息
在動態的序列問題中,對於修改操做沒有動過的部分。咱們能夠考慮把這些地方的求解的結果保存並複用,從而達到優化程序效率、下降複雜度的目的。
給定一個長度爲 N 的序列,支持:
修改一個位置的值
查詢一個子區間的元素和
解:
線段樹每一個節點維護對應子區間的和
區間覆蓋:
對於一個區間 [l, r],咱們能夠將其分解爲線段樹上 O(\log N) 個節點的並;
這裏的分解是指,咱們選取的區間並起來剛好爲 [l, r]。且選擇的區間不會相互重疊
修改操做中,爲了維護線段樹性質,須要修改總共 O(\log N) 個節點
查詢操做,將區間拆爲 O(\log N) 個區間的並,從而優化查詢的複雜度
總時間複雜度 O(\log N)
一種支持單點修改和查詢前綴和的數據結構
複雜度爲 O(\log N),可是常數很小
定義 lowbit(x),表示將 x 寫成二進制後,只保留二進制下最低一個 1 對應的整數
例:lowbit(1001100) = 100, lowbit(1000) = 1000
十進制:lowbit(76)=4, lowbit(8) = 8
對一個數組 a[],咱們構造數組 c[],其中
c[i] = sum (. a[i - lowbit(i) + 1 … i] )
巧妙的事情來了:
咱們查詢 a[] 的前綴和只須要訪問 c 中 log N 個節點
修改 a[] 中任意一個元素的值,只須要同時修改 c 中的 log N 個節點
因而能夠在 O(\log N) 的時間內支持單點修改、前綴和查詢
樹狀數組 如圖:
動態規劃是noip最重要的知識點之一 剛開始學的時候理解起來有些困難 入門理解請見:
轉送們
三要素:階段、狀態、決策
三前提:子問題重疊性、無後效性、最優子結構性質
動態規劃是對問題空間進行的分階段、有順序、無重複、決策性的遍歷求解類比有向無環圖及其拓撲序
還有一些例題就不一一列舉了 ,你們能夠上各類oj上去切
揹包問題(Knapsack problem)是一種組合優化的NP徹底問題。問題能夠描述爲:給定一組物品,每種物品都有本身的重量和價格,在限定的總重量內,咱們如何選擇,才能使得物品的總價格最高。(來自百度百科)
簡單來講 揹包就是DP中比較重要的一個分支
eg:
eg:
僞代碼:
經典例題:
僞代碼(不保證能過):
優化「階段」 ——倍增優化
優化「狀態」 ——狀態壓縮動態規劃
優化「轉移」 ——矩陣乘法加速、數據結構優化、單調隊列優化、斜率優化
僞代碼:
DP就像物理中的力學 又重要又難