算法基礎課&提高課 筆記要點 + OIer必備小知識


/qq/cy/kel/kk/dk/xyx/jk/qiang/ruo/ts/yun/yiw/se/px/wq/fad/youlQQ截圖20210810193431.png QQ截圖20210810193619.png
(表情顯示不出來的話戳這里QwQ)

$ {\color {hotpink} {\Large \mathbf{{♥\ I\ Iove\ OI\ ♥}}} }$

“學習方法?學習方法的話哈就只有一條,啊只有一條,一定要提高自制力,啊這個是萬變不離其宗,啊老生常談的一個問題,只要自制力上去了,一切都不是問題。”——yxc
QQ截圖20210803205818 - 副本.png
愛了愛了,謹記為人生格言之一qwq~

基礎課


基礎算法

  • 有單調性一定可以二分,沒有單調性可能可以二分
    二分的本質是邊界,區間可以一分為二,一邊滿足性質,一邊不滿足性質
    求最小的最大,求最大的最小也常用二分

  • 雙指針:O(N^2)通過一定性質轉化為O(N)

  • hash:模數取大於hash數組長度的一個質數(可以提前打表尋找合適的模數),且離2的整數次冪盡量遠,刪除操作一般不真的刪除,打個標記即可
    開放尋址法 數組需開2~3倍的空間
    拉鏈法
    字符串hash : 1.進制取P=131或13331,模數取Q=2^64(使用unsigned long long自然溢出即可)時,一般不會發生沖突 2.不要把一個字母映射為0 3.可用O(1)時間判斷字符串

是否相等

  • ≥x的數中最小的一個
while (l < r)
{
    int mid = l + r >> 1;
    if (a[mid] >= x) r = mid;
    else l = mid + 1;
}
return l;
  • ≤x的數中最大的一個
while (l < r)
{
    int mid = l + r + 1 >> 1;
    if (check(mid)) l = mid;
    else r = mid - 1;
    }
return l;
  • 離散化:1.保序:排序判重二分
    2.不需保序:map/hash表

N×N的棋盤,N為奇數時,與八數碼問題相同。逆序奇偶同性可互達
N為偶數時,空格每上下移動一次,奇偶性改變。稱空格位置所在的行到目標空格所在的行步數為空格的距離(不計左右距離),若兩個狀態的可相互到達,則有,兩個狀態的逆序

奇偶性相同且空格距離為偶數,或者,逆序奇偶性不同且空格距離為奇數數。否則不能。
也就是說,當此表達式成立時,兩個狀態可相互到達:(狀態1奇偶性狀態2奇偶性)(空格距離%2==0)。
推廣到三維N×N×N:
當N為奇數時,N-1和N^2-1均為偶數,也就是任意移動空格逆序奇偶性不變。那么逆序奇偶性相同的兩個狀態可相互到達。
當N為偶數時,N-1和N^2-1均為奇數,也就是令空格位置到目標狀態空格位置的y z方向的距離之和,稱為空格距離。若空格距離為偶數,兩個逆序奇偶性相同的狀態可相互到達;

若空格距離為奇數,兩個逆序奇偶性不同的狀態可相互到達。
原文鏈接:https://blog.csdn.net/hnust_xiehonghao/article/details/7951173

搜索

  • dfs使用stack,空間為O(h)
    bfs使用queue空間為O(2^h)

  • BFS具有兩段性,單調性 => 最短路性質(當邊權相等時,隊列遞增,相當於dijkstra的優先隊列,將其轉化為dijkstra即可證明其正確性,第一次搜到即為最短路),適用於求

最小,基於迭代,不會爆棧
當邊權不相等時,如0和1,用deque + bfs做時每個點可能入隊多次,第一次入隊不一定為最優解,當做特殊的dijkstra做即可
當狀態空間很大時,bfs的優化(雙向bfs和A)才有效果
除朴素bfs每個點只入隊一次之外,deque+bfs,A
,dijkstra都可能入隊多次

  • 一個圖是二分圖當且僅當圖中不含奇數邊的環

圖論

  • 樹的重心
    定義:
    一棵具有n個節點的無向樹,若以某個節點為整棵樹的根,他的每個兒子節點的大小(即子樹的節點數)都<=n/2 ,則這個節點即為該樹的重心
    性質:
    刪除重心后所得的所有子樹,節點數不超過原樹的1/2,一棵樹最多有兩個重心
    樹中所有節點到重心的距離之和最小,如果有兩個重心,那么他們距離之和相等
    兩個樹通過一條邊合並,新的重心在原樹兩個重心的路徑上
    樹刪除或添加一個葉子節點,重心最多只移動一條邊

  • 若圖中每個點的出度均為1,則只能構成三種情況:1、一條鏈;2、一個環;3、一條鏈連接着一個環。此時求最小環除了Floyd之外還可以用並查集(記錄到根的距離,唯一可以

形成環的情況就是鏈頭的元素連到了鏈中的元素上,每次連邊操作都是將一個鏈頭連到另一條鏈上),搜索,tarjan等方法
在遍歷過程中若遇到走過的點,用當前走到的步數減去在此節點原先記錄的步數,便得到這個環的長度

  • 最短路注重問題的轉化和抽象(使原問題的方案和圖上的路徑一一對應)
類型 算法 時間復雜度 備注
最短路 單源最短路 所有邊權非負 朴素Dijkstra O(N ^ 2) 適用於稠密圖。基於貪心性質,每次找未拓展的點中dis最小的一個進行拓展,拓展過的點的dis一定是最優解
堆優化Dijkstra O((M + N) * log N) 適用於稀疏圖。使用堆優化“找最小”的操作。手寫堆可任意修改堆中元素,保證堆的大小不超過N,而使用stl堆會有冗余(用vis標記每個元素,第一次出隊拓展后再出隊就不再拓展),堆的大小不超過M,但由於M < N^2,logM < 2logN, 所以時間復雜度影響不大
存在負權邊,但最短路徑上不存在負權回路 Bellman-Ford O(NM) 可用於求經不超過k條邊的最短路(SPFA不行)。迭代k次后的dis表示從起點經過不超過k條邊到每個點的最短距離,若迭代N次后仍有結點被更新,說明這條最短路徑上至少有N條邊,則一定是負環(但由於復雜度較高,一般用spfa判斷負環)
SPFA 一般O(M),最壞O(NM) 在迭代過程中只有dis[U]改變了,dis[V]才需要更新,故用隊列放入每次dis改變的點進行更新,每個點可能進隊出隊多次,vis表示其是否在隊列中。可用於判斷負環
多源匯最短路 Floyd O(N ^ 3) dis[i][j]表示所有從i到j,經過節點編號不超過k的路徑,可用於求最短路(不能處理負環);傳遞閉包;找最小環;dis[k][i][j]表示所有從i到j,經過邊數恰好為k的路徑,用倍增思想優化后可以用O(N ^ 3 * log_2 K)的時間復雜度求恰好(和bellman-ford不同)經過K條邊的最短路(可以包含負環)
最小生成樹 Prim 朴素Prim O(N ^ 2) 適用於稠密圖(鄰接矩陣),尤其是完全圖
堆優化Prim O(M log N) 代碼不如Kruskal好寫,一般不用
Kruskal O(M log M) 適用於稀疏圖,可以合並少於N - 1條邊,求最小生成“森林”,也可以在已有連通塊的基礎上繼續求最小生成樹。循環到第i條邊時可以求出由前i條邊(邊權<=d[i].w的邊)所構成的所有連通塊, 每次加邊都是將兩個連通塊進行連通,且此邊為新連通塊中的最大邊
  • 圖上限制次數的操作可以嘗試考慮分層圖

數學

  • 試除法判斷質數時循環邊界應寫成i <= N / i,i <= sqrt(N)效率較低,i * i <= N當N接近int上限時i*i會溢出為負數
  • N中最多只包含一個大於sqrt(N)的質因子
  • 小於2的整數既不是質數也不是合數
  • 質數定理:N足夠大時,1~N中有N /lnN個質數(實際范圍建議一定要自己根據數據范圍驗證並更正),而合數很稠密,接近N
  • 線性篩原理:
    從小到大枚舉p[j]
    1.if i%p[j] == 0 則p[j]是i的最小質因子,也是p
    [j] * i 的最小質因子,標記后即退出循環
    2.if i%p[j] != 0 則p[j]一定小於i的最小質因子,p[j]也一定是p[j] * i的最小質因子
    注:第二層循環枚舉j的時候可以不加上j <= cnt的邊界條件,因為i為合數時會在j=最小質因子時停止,i為質數時會在j=i時停止
  • a mod b = a - ⌊a/b⌋ * b
  • 若a,b互素,則a必然存在模b的逆元若a,b不互素,則a必然不存在模b的逆元
  • 逆元的遞推公式inv[0] = inv[1] = 1 , inv[i]=(M-M/i)*inv[M%i]%M (其中M為模數,要求為奇質數)
  • ax+by=(a,b)的通解為x = x0 - b/d * k, y = y0 + a/d * k(k為整數)
  • 由(a, b) = (b, a % b) ==> ax + by = by' + (a -a / b * b)x'可以推出exgcd的式子
  • 解同余方程ax+by=c的注意事項:
    (a,b)是ax+by能湊出來的最小正整數,ax+by一定是(a,b)的倍數(因為a,b都是其倍數)
    設d = gcd(a,b), 則d | c時才有解
    由於x0 / d不一定能整除,所以不能寫成x0 / d * c的形式,而應寫成c / d * x0的形式
    注意在(ll)c / d * x0的計算過程中轉換為ll防止int溢出
    通過不斷加減b / d可求int范圍內的通解,可以用mod (b / d)實現,而加減d 個 b / d就相當於加減一個b,所以mod B肯定也能得到一個解。求最小正整數解用(x0 % P + P) % P,P = abs(b / d);
  • d = a ^ b ^ c 可以推出 a = d ^ b ^ c.
    異或運算還具有消去律:ab=bc => a=c,與、或運算均不滿足該性質。
    現在給你其中有n對數和1個單獨的數(一對數指這兩個數相等),要求用O(n)的時間找出單獨的數,把所有的數字異或起來即可
  • EXCRT
  • 高斯消元 O(N^3)
  1. 找主元(為了保證精度應找當前列中各行絕對值最大的數)
  2. 把當前行與絕對值最大數所在行交換
  3. 將當前行同除以該數,使該系數變為1
  4. 將下面各行的當前列系數都減為1
  5. 從最后一行往前將倒三角矩陣化為對角線矩陣
    每個自由變量的每個取值都對應一組解,自由變量的個數就是高斯消元后N - 非零行的個數
  • SG定理:游戲和的SG函數等於各個游戲SG函數的Nim和(Nim和:各個數相異或的結果)
    多個獨立局面的SG值,等於這些局面SG的異或和
  • 求多個元素兩兩乘積之和 = 所有元素和的平方 - 平方和,如2ab+2bc+2ac=(a+b+c)2-(a2+b2+c2)

其它

  • 系統為某一程序分配空間時,所需時間與空間大小無關,與申請次數有關
    stl常見用法 見 “算法基礎課 第二章 數據結構(三)”

  • 調試代碼常用方法:1.輸出中間變量的結果 2.刪去(注釋掉)一部分代碼進行檢查
    debug之前先通讀一遍,過濾掉大多錯誤

  • 每道題寫代碼調試出現的錯誤可以標注在代碼注釋中方便以后再次注意

  • 數組越界了什么錯誤都有可能發生,不一定是段錯誤或RE

  • C++ if 的判斷條件,非0即真(負數也為真)
    if (A || B) 如果A是真,就不會計算B表達式了
    if (A && B) 如果A是假,就不會計算B表達式了

  • 對於二進制數i,for (int j = (i - 1); j; j = (j - 1) & i)可以枚舉出其所有非全集子集j(改成int j = i即包含了全集),比如 i = (1101)2的時候,j可以枚舉到:1101、1100、1001、1000、0101、0100、0001、(0000)


提高課


DP

  • 閆氏DP思考法
    狀態表示:集合與屬性(max/min/數量)
    狀態計算:集合的划分(原則:不漏/一般不重復)
    重要的划分依據:最后
    看當前是由哪些狀態轉移過來,或者當前可以更新哪些狀態
    閆氏最優化問題分析法:
    最優化問題可以從集合角度考慮,不重不漏
    在一個有限集合中求最值或個數
    若是無限集合,常證明最優解在一個有限子集中

  • 當狀態較少無法轉移時可以考慮增加一維狀態轉移
    增加數組的維度,將更多的條件和狀態記錄下來,有可能更利於進行轉移
    如果遇見選擇或者不選擇狀態的題目時,可以考慮背包,可以額外用數組的一維0或1表示拿與不拿的狀態

背包問題:for物品 for體積 for決策
對於f[i個物品,體積j]=價值w
體積最多是i 初始化全部為0 枚舉時體積j>=0
體積恰好是i 初始化f[0,0]=0,f[0,j]=+oo 枚舉時體積>=0
體積至少是i 初始化f[0,0]=0,f[0,j]=+oo 枚舉時體積可以為負數(>=負數可以轉換為>=0)

求方案數初始化總結
二維情況
1、體積至多j,f[0][i] = 1, 0 <= i <= m,其余是0
2、體積恰好j,f[0][0] = 1, 其余是0
3、體積至少j,f[0][0] = 1,其余是0
一維情況
1、體積至多j,f[i] = 1, 0 <= i <= m,
2、體積恰好j,f[0] = 1, 其余是0
3、體積至少j,f[0] = 1,其余是0
求最大值最小值初始化總結
二維情況
1、體積至多j,f[i,k] = 0,0 <= i <= n, 0 <= k <= m(只會求價值的最大值)
2、體積恰好j,
當求價值的最小值:f[0][0] = 0, 其余是INF
當求價值的最大值:f[0][0] = 0, 其余是-INF
3、體積至少j,f[0][0] = 0,其余是INF(只會求價值的最小值)
一維情況
1、體積至多j,f[i] = 0, 0 <= i <= m(只會求價值的最大值)
2、體積恰好j,
當求價值的最小值:f[0] = 0, 其余是INF
當求價值的最大值:f[0] = 0, 其余是-INF
3、體積至少j,f[0] = 0,其余是INF(只會求價值的最小值)
作者:小呆呆
鏈接:https://www.acwing.com/blog/content/458/

  • 完全背包:求前綴的屬性
    多重背包:求“滑動窗口”的屬性
    拓展:https://blog.csdn.net/qq_35577488/article/details/108932689
    https://blog.csdn.net/qq_35577488/article/details/108937340

  • N<=20 可能考慮狀壓dp

  • 當一層狀態只與前幾層狀態有關時,可以進行滾動數組優化,注意滾動數組的每一層是否都要初始化

  • 數位dp:分成貼合上界和不貼合上界兩種情況,不貼合上界情況可以用數學方法直接算或是用f記憶化存儲答案(有時可以預處理),貼合上界的情況對下一位繼續進行分類

  • 單調隊列盡量用數組寫,deque比較慢
    head=0,tail=0或tail=-1皆可
    某些題目里隊列中的第一個元素很特殊,會取0,那么此時head = tail = 0會表示定義了一個隊列,且隊列中已經有一個元素0了,而不是定義了一個空隊列。

  • 斜率優化dp:
    怎么在維護的凸包中找到截距最小的點?
    相當於在一個單調的隊列中,找到第一個大於某-個數的點。
    1.斜率單調遞增,新加的點的橫坐標也單調遞增。
    在查詢的時候。可以將隊頭小於當前斜率的點全部陽掉。
    在插入的時候,將隊尾所有不在凸包上的點全部刪掉。
    2.斜率不再具有單調性,但是新加的點的橫坐標一定單調遞增。
    在查詢的時候,只能二分來找。
    在插入的時候,將隊尾不在凸包上的點全部刪掉。
    3.新加的點的橫坐標不再具有單調性,但是斜率一定單調遞增。
    可以倒序DP,設計一個狀態轉移方程,讓 sumTi是橫坐標,sumCi是斜率中的一項。仍然可以用單調隊列維護凸殼,用二分法求出最優決策——摘自《算法競賽進階指南》第五版327頁。
    4.斜率、新加的點的橫坐標都不再具有單調性
    用平衡樹維護(NOI2007)

  • 在進行狀態轉移時如果很多轉移都沒有用,可以進行路徑壓縮(或稱離散化),比如說在線性dp中dp[i] -> dp[j],通過將i->j的距離修改為 (j - i) % lcm(lcm是所有轉移步數的最小公倍數)將很長的區間變小,注意看一下dp[j]最后一步是可以由哪些狀態轉移過來的,這些狀態必須在壓縮后可以走到(比如人為的再將距離加上一個lcm)
    例如 [NOIP2005 提高組] 過河

  • 有時候題目中如“每張卡片僅能使用一次”這樣的讓人以為是搜索回溯的條件也可以轉化為“每種卡片最多使用k次”(同一種卡片完全相同)這樣的與動態規划相接近的條件

  • 數據范圍較小時考慮狀壓dp或搜索

  • 用后序遍歷求一棵樹的dfs序列可用於dp(如有依賴的背包問題,[NOIP2006 提高組] 金明的預算方案),一個點的左兄弟就是dfs進入這個點時dfs序列中的最后一個點

貪心

  • 貪心證明方法:
    ❶反證法
    例如:若最優解不是按從小到大排序,則必然存在相鄰兩項A,B且A>B
    ❷A:貪心結果 B:最優解
    A=B 即證A<=B 且 A>=B
    B<=A 方案合法
    A<=B 調整法:
    假設A與B方案不同,找到第一個不同的位置,將貪心方案與最優方案等價互換,對結果沒有影響,從貪心方案同樣能得到最優解(最優解一定能替換成算法所得解)
    ❸數學歸納法
    ❹鄰項交換法(特別是排序問題)
    【貪心做法建議先證明,找反例調試】

  • 證明最小值技巧:先證ans>=x,再構造一種方案證可以取等,則最小值為x

  • 證明任意一個方法都對應一個解,任意一個解都對應一個方法,兩個問題一一對應,包含集合相同

搜索

  • dfs求最小步數:
    1.記一個全局最小值
    2.迭代加深

  • Floodfill 在線性時間內找到某點所在連通塊

  • meet in the middle 適用於狀態數量很大的題目,一般是指數級別,復雜度從O(xn)優化為O(2*x(n/2)),注意要求搜索圖中的節點狀態都是可逆的

  • bfs第一次入隊為最優解(第一次入隊后判重)
    dijkstra第一次出隊為最優解(第一次出隊后判重)
    A*:(不需判重)
    只有終點能保證第一次出隊時為最短距離
    除終點之外的點可能被擴展多次

  • 棧空間默認是1MB

  • 走迷宮模型,每個點只走一次,不需回溯;走狀態模型,需要回溯后再走到下一個狀態

  • 搜索順序:按“組合”的方式來搜,而不是按“排列”的方式,可以避免重復,比如按下標升序搜索

  • 盡量減少搜索樹的分支,如“把x加到之前任意一組”改為“每一組都盡量枚舉完,把x加到最后一組中”

  • 先保證正確性再優化
    剪枝:
    1.優化搜索順序(一般優先選擇搜索分支較少的節點,要求能搜索到所有方案)
    2.排除等效冗余(如按“組合”順序進行枚舉)
    3.可行性剪枝(如上下界剪枝)
    4.最優性剪枝
    5.記憶化搜索

  • 迭代加深:適用於分支較深,但答案所在層數較淺,且比bfs所用空間少,且IDA*可以配合迭代加深使用

圖論

半歐拉圖:只有歐拉路徑,沒有歐拉回路的圖。
判定:
首先是個連通圖
如果所有點的度數都是偶數則為歐拉圖。
如果只有兩個點的度數為奇數,其它點的度數都是偶數,該圖為半歐拉圖。
尋找歐拉路徑和歐拉回路的方法:
從起點出發,若當前點是x,遞歸走與x點所有相連的沒有走過的路徑,走完把x按順序存起來, 最后倒序輸出就是歐拉回路。
在半歐拉圖中,要求歐拉路徑的話,從度數為奇數的點出發就行了。

  • 乘法最短路
    對w1w2w3取log,log(w1w2w3) = logw1 + logw2 + logw3,可轉化為一般的最短路問題
    當0<=w<1時,logw<0,求log(w1w2w3)的最大值可變形為求(-logw1) + (-logw2) + (-logw3)的最小值,即非負權圖上的最短路問題,可用dijkstra
    當w>=0時,邊權可正可負,用spfa即可

  • 鄰接表忘記初始化表頭可能會導致TLE

  • 對於拓撲圖最短路, 不管邊權正負,均可按拓撲序搜索

  • dp大多相當於拓撲圖上的最短路問題
    當依賴關系存在環不可以用dp,但可以轉化為最短路,dp和最短路都可用看成在集合上的最優化問題。
    一些dp可以用高斯消元

  • dijkstra的適用前提:點第一次出堆即為最優解,不會再被其它點更新,也只有這樣才能保證其時間復雜度
    bellman-ford的適用前提:最短路經過邊數小於n - 1

  • 多源多匯最短路可以用超級源點超級匯點

  • 分層最短路可以從dp的角度考慮

  • 對於邊權0和1的最短路,deque+bfs可以保證在最壞情況下仍是線性復雜度

  • 求方案數:先求出最優解,再分別求出每個子集中等於最優解的元素個數。DP類型的問題狀態更新必須滿足拓撲序。對於最短路求解方案數,考慮最短路樹(簡單考慮可以認為邊權都大於0),圖上不可能有權值和為0的環(會導致方案數無數種)
    求解單源最短路徑的算法可以分為三大類:
    (1)BFS:每次擴展一層,只入隊一次,出隊一次,被更新的點的父節點一定已經是最短距離了,並且這個點的條數已經被完全更新過了,一定可以得到拓撲圖。
    (2)Dijkstra(包含雙端隊列廣搜):每個點只出隊一次,第一次出隊的時候一定已是最小值了,且已確定最短距離的點不可能再被更新,可以構成拓撲性質
    (3)Bellman_ford(spfa):不一定滿足拓撲序。因為某個點可能入隊多次,出隊多次,被更新距離的點也是不確定的。
    如果圖中存在負權邊,可以先使用spfa求出最短路徑;然后建立最短路徑樹,用dis[v] == dis[u] + w[i]在這個樹上統計最短路徑次數,注意u的最短路徑數應當提前算完

  • (最小生成樹)如何證明當前邊一定可以被選:
    假設不選當前邊,最終得到一棵樹,再把當前邊加上,必然出現一個環,在環上一定可以找出一條長度不小於當前邊的邊,那么將當前邊替換上去,結果一定不會變差

  • DAG常用拓撲排序得到遍歷順序,有向有環圖可以想到縮點,利用DAG的特性,許多算法在 DAG 上可以有更優/更方便的解。

  • 無向圖的最小生成樹一定是最小瓶頸生成樹

  • 如果邊權可正可負,求將所有點連通的最小邊權和,可以把負權邊先全部選上再kruskal

  • 一定存在一棵次小生成樹和最小生成樹只有一條邊不同,對最小生成樹插入非樹邊刪除環上樹邊的方法可以求嚴格次小生成樹和非嚴格次小生成樹

  • 判斷負環:常用cnt[x]記錄1到x的最短路所包含的邊數,若cnt[x] >= N則說明有負環,注意初始化時將所有點都加入隊列,dis可初始化成任意值(可以從虛擬源點的角度考慮)。
    優化:❶trick:當更新次數超過2 * N時,圖中還有可能存在負環,可以直接判斷存在,如果N比較小而M比較大的話可能考慮將更新次數上限調大,這是損失一定正確性來優化時間
    ❷將隊列換成棧(不要用stl,用手寫,隊列下標是遞增的,進隊次數可能會很多,所以訪問到的下標可能很大,需要循環隊列。但棧不管插入多少次,最多只會用前n個位置,不需要循環。),或把基於bfs的spfa改成基於dfs,但注意可能嚴重降低負環不存在時計算最短路的效率

  • 差分約束
    ❶求不等式組的可行解
    源點需要滿足的條件:從源點出發可以走到所有的邊。
    步驟:
    [1]先將每個不等式xi <= xj + c,轉化成一條從xj走到xi,長度為c的一條邊
    [2]找一個超級源點,一定可以遍歷到所有邊(可以遍歷到所有點則可遍歷所有邊,反之不一定成立)
    [3]從源點求一遍單源最短路
    如果存在負環,則無解;如果沒有負環,則dis[i]就是原不等式組的一個可行解
    ❷求最大值或者最小值(這里的最值指的是每個變量的最值)
    結論:如果求的是最小值,則應該求最長路;如果求的是最大值,則應該求最短路;(xi <= xj + c可直接用最短路,負環無解;xi >= xj + c可直接用最長路,正環無解,也可轉化為xj <= xi + (-c) ; xi = xj可轉化為xi <= xj且xi >= xj ; xi < xj可轉化為xi <= xj - 1)
    可行解只能滿足相對大小關系,如果不等式中有絕對值條件xi <= C才可以求最值,對於這樣的條件建立一個超級源點0,然后建立0->i,長度是c的邊即可。
    以求xi的最大值為例:對於所有xi在左邊的關系x_i <= x_i-1 + c_i-1都可以放縮:x_i <= x_i-2 + c_i-2 + c_i-1<=...<=x_0 + c_0 + c_1 +...+c_i-1,即x_i <= c_0 + c_1 + c_2 +..+c_i-1,這里的c_0~c_i-1即為從0到i的一條路徑上的邊,邊權和即為xi的一個上界,xi的最大值即為所有上界的最小值,也就是從0到x的最短路;同理,xi的最小值即為從0到x的最長路(若從0到不了i,dis[i] = inf,意味着不等式鏈有殘缺,xi可以取任何值)
    若既要判斷負環又要求最短路,可以初始化把所有的點都壓入隊列,但除了dis[1] = 0之外dis都為inf

  • tarjan、縮點(可建圖)、依據拓撲序遞推(用tarjan縮點所得連通分量編號遞減的順序一定是拓撲序)
    將有向圖變為一個SCC最少需要加max{入度為0的點數,出度為0的點數}條邊

  • e-DCC性質
    不管刪掉哪條邊圖依然聯通
    e-DCC <=> 任意兩點之間都存在兩條不相交(沒有公共邊)的路徑
    橋的兩個端點不一定是割點
    e_DCC中,每個邊只會屬於一個e_DCC,且橋不會屬於任何e_DCC
    e-DCC不一定是v-DCC
    將無向圖變為一個e-DCC最少需要加⌊ cnt + 1 / 2 ⌋條邊,其中cnt是葉子節點個數(度為1)
    e-DCC縮點后是一棵樹(將一棵樹變為一個e-DCC 至少增加的邊數 =( 這棵樹總度數為1的結點數 + 1 )/ 2)

  • v-DCC性質
    每個割點至少屬於兩個v-DCC
    兩個割點之間的邊不一定是橋
    v-DCC不一定是e-DCC
    一個點也是一個v-DCC
    ->關於Tarjan算法中用dfn不能用low的問題

  • 二分圖(一般都是無向圖,但兩點之間只建一條有向邊)
    一個圖是二分圖 <=> 圖中不存在奇數環 <=> 染色不存在矛盾
    最大匹配數 = 最小點覆蓋 = 總點數 - 最大獨立集 = 總點數 - 最小路徑點覆蓋

  • 字典序拓撲排序可以用優先隊列

  • 查分約束 求最小值(最長路)

  1. 邊權可正可負 spfa O(KM)
  2. 邊權非負 tarjan O(N + M) 若有解,每個SCC內的邊都為0,一旦有正邊就會構成正環
  3. 邊權為正 拓撲排序 O(N + M) 一旦有環則無解
  • 如果對於兩個圖N, M,一個圖的所有結點都要向另一張圖的所有結點連邊,總共要連N * M條邊,但如在中間加一個虛擬點,左圖所有結點向虛擬點連邊,右圖所有結點向虛擬點連邊,則可優化為總共只需要N + M邊
  • 寬搜的倒序就是拓撲序

數據結構

  • 在01區間中求從后往前第一個為0的位置可以用並查集優化
  • 並查集常見操作:1.在根節點記錄每個集合大小
    2.用帶權並查集記錄每個點到根節點距離(相當於每個點出度為1的有向圖)
inline int find(int X){
    if (fa[X] == X) return X;
    int root = find(fa[X]);//防止多次合並成一條鏈的情況,必須先更新父節點
    d[X] += d[fa[X]]; //d[X]是 X 到 fa[X] 的距離,再加上fa[X] 到 root的距離即可
    return fa[X] = root;
}

if (fx != fy){
	f[fx] = fy;
	d[px] = -d[x] + d[y] + s;
} //注意這里x -> y長為s的邊轉換成了fx -> fy的一條邊

將元素分成k類:1.帶邊權並查集 記錄每個點到根節點距離 維護元素相對關系 時間復雜度與k無關,k較大時宜選擇
2.擴展域並查集 將每個元素按分類拆成多個條件,同一個集合中一個條件成立則其它條件也成立 相當於枚舉各類情況 每次操作O(k)
區間染色問題:長為n的序列,每次將l到r的數全染成c色,求最終的序列顏色。
我們考慮將染色反着來,則一個數若被染色一次,則這就是它的最終顏色,不會改變。
所以下次若在遇到這個數則需要跳過,也就是對已經染色的區間[l,r],我們令其中的所有數都有一個指針,以指向右邊第一個還沒有操作的數。
於是我們令fa[i]表示i右邊(包括i)第一個沒有染色的數,將i染色后,令fa[i] = find(i + 1),用並查集操作即可。

  • 具有無向(或者說雙向)的傳遞性關系(若已知A與B,B與C的關系,則可以推出A與C的關系)可以用並查集或傳遞閉包,而具有有向的傳遞性關系只能用傳遞閉包
  • 敵人的敵人是朋友(例:[NOIP2010 提高組] 關押罪犯)在對敵人進行合並時,注意一定要將find其祖先進行合並,即f[x] = find(enemy[y])
  • 循環隊列
int hh = 0, tt = 0;
Q[tt++] = X;
...
while(hh != tt){
    int U = Q[hh++];
    if (hh == maxn) hh = 0;
    ...
    Q[tt++] = V;
    if (tt == maxn) tt = 0;
} 
  • 對頂堆:1.小根堆的所有元素大於等於大根堆的所有元素
    2.大根堆的元素個數與小根堆元素個數相等或多1

  • ST表建議預處理log函數省時間,for (int i = 2 ; i <= N ; i++) lg[i] = lg[i >> 1] + 1;
    對於st[i][j]=f(st[i][j-1],st[i+(1<<j-1)][j-1]),我們將i,j的順序調換一下可以提高效率

  • BIT(樹狀數組)的原數組下標一定要從1開始,它只支持滿足“區間減法”的操作,即知道[1, r] [1, l - 1]的信息,就可以推出[l,r]的信息,如區間和,區間異或和,乘積。
    樹狀數組初始化:
    1.用update操作 O(NlogN)

  1. 先求前綴和數組S,tr[x] = S[x] - S[x - lowbit(x)] O(N)
  2. tr[x] = a[x] + [x - lowbit(x) + 1, x - 1]的區間和
for(int x = 1;x <= n;x ++) {
    tr[x] = a[x];
    for(int i = x - 1;i >= x - lowbit(x) + 1;i -= lowbit(i))
         tr[x] += tr[i];
}

二分 + 樹狀數組可以同時實現平衡樹的刪除元素和查詢第k大元素的操作

  • 線段樹是否需要維護額外信息:通過左右子區間的屬性能不能算出父區間的屬性?

  • 可持久化的前提:本身的拓撲結構不變

  • Trie的trick,如果空間限制不太夠的話,可以根據根據空間限制開trie數組(注意數組空間略小於空間限制)

  • 單詞A中單詞B出現的次數,其實就是看在A所有的前綴中以單詞B為后綴的次數,與next數組類似

  • 雙隊列優化:對一個單調隊列中的數進行修改操作后再放入隊列,如果修改后不一定是隊列最大值最小值(不能確定放隊頭還是隊尾),同時多次操作得到的一系列新數都有序的話,可以再開一個單調隊列存儲新數,每次的最大值即為單調兩隊列隊尾的最大值
    例如“合並果子”問題(哈夫曼樹)中的O(nlogn)堆做法就可以優化為O(n)桶排 + 雙隊列做法

  • 一般區間操作首先要想差分

數學

  • 用分治可以快速求等比數列之和

  • gcd具有結合律、交換律,gcd(a−nb,b)=gcd(a,b),gcd(a,b)=gcd(a,−b),gcd(a,a)=a,
    gcd(ka,kb)=k*gcd(a,b)
    當k與b互質,gcd(ka,b)=gcd(a,b),也就是約掉兩個數中只有一個數含有的因子不影響gcd。特殊地,當k=2時,可用於高精度求gcd
    (a1, a2, a3,..., an) = (a1, a2 - a1, a3 - a2, ..., an - a(n-1))可用於差分 + 線段樹實現區間加減 + 區間求gcd

  • 異或和同樣可以預處理前綴異或和數組,[l, r]的異或和 = S[r] ^ S[l - 1]

  • 1/2 + 1/3 + 1/4 + ... + 1/N = ln N + c ≈ logN
    1/2 + 1/3 + 1/5 + ... + 1/N(分母是質數) ≈ loglogN

  • N的因子有d,則也有N/d,設d≤N/d,得d≤√N,即N一定有小於等於√N的因子,利用此性質可以用1~√N的質數的倍數篩出N范圍內多個較小區間的質數(二次篩法)。數論中數據范圍較大時,常嘗試用小范圍求出大范圍,起到四兩撥千斤的作用

  • ⌈N / P⌉ * P大於等於N的最小的P的倍數,即(N + P - 1)/ P * P,注:⌈N / P⌉ = ⌊(N + P - 1) / P ⌋ , 上取整和下取整函數為floor和ceil

  • 給一個數N分解質因數的最壞時間復雜度是√N

  • N!中因子P的次數為⌊N/P⌋ + ⌊N/P/P⌋ + ⌊N/P/P/P⌋ + ... + ⌊N/(P^k)⌋ ( P^k<=N)

  • 除了全等數列之外,一個數列的前三項不可能既等差又等比

  • 正難則反, 補集思想

  • 取模余數在數學定義中是非負的,但C++中負數取模可能是負數,需要先+P再%P轉化。取模運算中有減法時一定要注意是否需要轉化

  • 1~N中所有數的數的約數個數之和為N/1 + N/2 + N/3 + ... + N/N ≈ NlogN,int范圍內(0≤N≤2147483647)約數最多的數的約數個數為1600個

  • 關於約數的題目一般不去對每個數求約數(O(N√N)),而是去考慮其倍數(N / 1 + N / 2 +...+ N / N = O(NlogN))

  • 對於y = c / (x + a),a,c為正常數,x,y皆為正整數,要求x的個數,即為求c的約數個數

  • 10 ^ 6內有78498個質數

  • 當題目沒有思路的時候可以嘗試想想題目有什么性質

  • lcm(x, y) = x * y / gcd(x, y)

  • 求一個數N的因數從1~√N進行枚舉需要O(√N),如果先篩出1~√N內的質數,再對N質因數分解,用dfs構造所有約數可以使時間復雜度優化到O(√N / log(N))

  • C++中余數的正負與被除數的正負相同,與數學定義有區別
    5 % -2 = 1, -5 % 2 = -1, -5 % -2 = -1

  • 當相乘結果爆long long,但超出范圍不多,相加不爆long long時,可以用龜速乘

  • 矩陣乘法具有結合律,不具有交換律。矩陣快速冪中求快速冪的矩陣不能有變量,應把變量化成不帶變量系數的式子

  • 當dp的每一個狀態和上一個狀態都有一個線性遞推關系,且各項系數不變時,可以用矩陣快速冪優化(當然矩陣不能太大,矩陣乘法的復雜度是O(N^3))

  • 快速冪的乘法運算記得先(ll)強轉類型

  • 求組合數時先看一下數據范圍及是否取模,判斷是否需要高精度。
    當N很大時,C(N, K)可以分解質因數再約分轉化為乘法高精度來求。
    對於高精度,在數據范圍可以的情況下,用遞推式只需高精度加法,較好寫。
    用求逆元的方法需保證模數是質數,
    注意初始化fac[0] = fac[1] = inv[0] = inv[1] = 1; inv[i]=(M-M/i)*inv[M%i]%M 遞推求inv之后還要再進行階乘;
    模數為質數時才可以用lucas
    當C(M, N)中M很大N很小時,可以直接用定義求組合數效率較高,即M * (M - 1) * ... * (M - N + 1) / N!

  • 組合計數:遞推法,隔板法,加法原理,乘法原理,排列組合(Lucas, Catania數列/明安圖數),容斥原理(補集思想)

  • 對於同一直線(斜率不是0或無窮大)上的點,其規律常常與坐標或橫縱坐標差的gcd有關,如(a, b)和(c, d)所在直線上,這兩點之間的整數格點有gcd(c - a, d - b) - 1個

  • 對於不等式組0 ≤ a1 ≤ a2 ≤ ... ≤ ak ≤ N, ai是整數,求a序列的方案數
    1 . 可以構造差分數組x1 = a1, x2 = a2 - a1, x3 = a3 - a2, ... , ak = ak - a(k - 1), 相當於x1 + x2 + x3 + ... + xk ≤ N,xi 為非負整數 <===> (對於x為非負整數的問題進行映射,轉化為正整數,則可以使用隔板法) (x1 + 1) + (x2 + 1) + ... + (xk + 1) ≤ N + k, 即y1 + y2 + ... + yk ≤ N + k, yi 為正整數 轉化為線性不等式(如果是等號,可以直接用C(N + k - 1, k - 1)),使用隔板法,相當於有N + k個1, 在N + k個空隙(開頭不能插,但末尾可以插)中插入k個板,其中第k個板表示其右邊的1全部不選,這樣就可以使前k - 1個板隔開的數的總和為≤N + k的所有可能的數,方案數即為C(N + k, k)
    2 . 將非嚴格單調遞增序列轉化為嚴格單調遞增序列,令b1 = a1, b2 = a2 + 1, b3 = a3 + 2, ..., bk = ak + k - 1, 則a序列可以映射為0 ≤ b1 < b2 < b3 < ... < bk ≤ N + k -1 , 用C(N + k - 1 - 0 + 1, k)
    3 . 實際上我們在意的是元素的相對關系,對於L ≤ a1 ≤ a2 ≤ ... ≤ ak ≤ R的問題可以映射轉化為0 ≤ a1 ≤ a2 ≤ ... ≤ ak ≤ N,令N = R - L即可
    4 . 如果k不確定,需要枚舉的話,需要使用組合數公式C(N + 1, 1) + C(N + 2, 2) + ... + C(N + M, M) = (C(N + 1, N + 1) - 1) + C(N + 1, N) + C(N + 2, N) + ... + C(N + M, N) = C(N + 2, N + 1) - 1 + C(N + 2, N) + ... + C(N + M, N) = C(N + M + 1, N + 1) - 1

  • 對於x1 + x2 + ... + xn = M, xi ≥ 0
    <===> 設yi = xi + 1, y1 + y2 + ... + yn = M + n, yi > 0, 隔板法即可,方案數為C(M + n - 1, n - 1)

  • 卡特蘭數的判斷:

  1. 遞推式f[n] = f[1] * f[n - 1] + f[2] * f[n - 2] +..., 如二叉樹個數
  2. 挖掘性質:任意前綴中0的個數≥1的個數
    C(2n, n) - C(2n, n - 1) = C(2n, n) / (n + 1)
    若有f[3] = 5 ,可考慮卡特蘭數。
    卡特蘭數列:1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786
    斐波那契數列:1、1、2、3、5、8、13、21、34
  • 容斥原理常與集合的並集有關,可用二進制枚舉所有並集情況,時間復雜度為O(2 ^ N), 題目N的范圍一般很小
  • 整除分塊:用O(√n)求∑⌊n / i⌋,定義使⌊a/g(x)⌋ = ⌊a/x⌋的最大的g(x)(即⌊a/(g(x)+1)⌋ < ⌊a/x⌋)等於⌊a/⌊a/x⌋⌋
  • E(aX + bY) = aE(X) + bE(Y)(a,b是X,Y發生的概率,常數項可以直接提出)
    期望問題往往起點唯一,終點不唯一,常從終點倒推至起點
  • 線性篩素數
void get_prime(int N){
    for (int i = 2 ; i <= N ; i++){
        if (!vis[i]) prime[++cnt] = i, vis[i] = i;
        for (int j = 1 ; prime[j] <= N / i ; j++){  //注意<= N / i這里必須有等號
            vis[prime[j] * i] = prime[j];
            if (i % prime[j] == 0) break;
        }
    }
} 
  • 枚舉一個數的所有因數
for(int i=1;i*i<=x;i++) 
 if(x%i==0){
                ...
                int j=x/i;//得到另一個因子
                if(i==j) continue; 
                ...
    }
  • gcd(x,a0)=a1 ---> a1 | x 且 gcd(x/a1,a0/a1)=1
    lcm(x,b0)=b1 ---> x | b1 且 gcd(b1/b0,b1/x)=1
    (例:[NOIP2009 提高組] Hankson 的趣味題)

  • 有向無環圖上移棋子
    一個棋子 先手必勝 <=> SG(S) != 0
    多個棋子 先手必勝 <
    => SG(S1) ^ SG(S2) ^ ... ^ SG(Sk) != 0

其它

字符(待更)

  • 字符串函數
#include<cctype>
1、isalpha(x) 判斷x是否為字母
2、isdigit(x) 判斷x是否為數字
3、islower(x) 判斷x是否為小寫字母
4、isupper(x) 判斷x是否為大寫字母
5、isalnum(x) 判斷x是否為字母或數字
6、ispunct(x) 判斷x是否為標點符號
7、isspace(x) 判斷x是否為空格
8、toupper(x) 如果x是小寫字母,將其轉換成大寫字母
9、tolower(x) 如果x是大寫字母,將其轉換成小寫字母
#include<string>
1、s.erase(x,y) 表示將字符串s從x位置起刪除y個字符
2、s.insert(x,y) 表示將字符串y(或字符y)插入到s的x位置處
3、s.push_back(x) 表示在s的末尾插入字符x
4、reverse(s.begin(),s.end()) 將字符串s翻轉
  • s.substr(pos, n)中若pos的值超過了string的大小,則函數會拋出一個out_of_range異常;若pos+n的值超過了string的大小,只拷貝到string的末尾

  • “空格+%c”或“%s”能夠忽略getchar緩沖區的空白字符
    scanf的格式匹配字符串中空格表示空白字符的占位符(任何空白字符都會自動匹配上這個空格,所以相當於這個空白字符被getchar掉了)
    而%s則會忽略前導的空白字符
    【為了防止空白字符的干擾,即使讀入一個字符也建議用%s】

  • 字母'A'的ASCII碼是41H(0100 0001B),字母'a'的ASCII碼是61H(0110 0001B),字母'A'與'a'的二進制后5位是相同的,所以無論是大寫字母還是小寫字母x,x & 31(1 1111B)的值就是x在字母表里的順序。

  • 對於輸入有很多字符的模擬題,如果在線操作比較復雜,可以嘗試用字符串保存輸入再進行處理

輸入輸出

  • 快讀快輸
template <typename TT> 
void inline rd(TT &x){
	x = 0;
	TT f = 1;
	char ch = getchar();
	for ( ; ch < '0' || ch > '9' ; ch = getchar()) if (ch == '-') f = -1;
	for ( ; ch >= '0' && ch <= '9' ; x = (x<<3) + (x<<1) + (ch^48), ch = getchar());
	x *= f;
}

template <typename TT> 
void inline wt(TT x){
	if (x < 0) x = ~(x - 1), putchar('-');
	if (x > 9) wt(x / 10);
	putchar(x % 10 + '0');
}

ios::sync_with_stdio(false) 需特別注意無輸出的錯位輸出的比賽常見錯誤

  • cin解鎖(ios::sync_with_stdio(false))不要和scanf 、getchar、puts、gets等混用(會因為緩沖區出現問題)
    ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);

  • sstream庫:stringstream ss 賦值有兩種方式, 1. ss(s) ; 2. ss << s; 使用ss可以先接收字符串,再將字符串作為 輸入流 >> 轉化成整型(會忽略字符串里 的空格、回車、tab)。 緩沖區中,getline(cin, s) 遇到回車會結束接收,並且刪掉的回車。

  • 對於一些題目需要注意輸入數據是否已經排好了順序*,有時候輸入數據並沒有說明保證有序,可能需要先sort一遍。建圖注意是否有自環重邊。注意如果輸入(x,y)時需要x<y,數據會不會出現x>y的情況

數據類型

  • 能用int盡量不用long long,容易MLE,在計算過程中int強轉ll即可

  • unsigned long long的范圍是[0,264-1],越界以后相當於對264求模(遇到范圍是263,取模264的這種題目,要想到用unsigned long long類型)
    long long的范圍是[-263,263-1],越上界后會從-263開始計數,越下界則從263-1開始計數

double可以存儲比unsigned long long更大的數字
原因是無符號 long long 會存儲精確整數,而 double 存儲尾數有限的精度和一個指數。
這允許 double 存儲非常大的數字(大約10^308 ),一個 double 中有大約15個(近16個)有效的十進制數字,
也就是說double的精度(15或16位)比long long(19位)位要小,但是double取值范圍為 -1.7976E+308 到到-4.94065645841246544E-324,正值取值范圍為 4.94065645841246544E-324 到 1.797693E+308。
對於long long
其64位的范圍應該是[-2^63 ,2^63],既-9223372036854775808~9223372036854775807。
long double 會保存二進制最高 64 位和指數
double的這種存儲方式也就意味着 2 的冪是不會對精度產生影響的

  • double fmod(double x,double y) 返回x除以y的余數。
    fmod()用來對浮點數進行取模,設x=k*n+h,則返回值為h(h和x的符號相同)。
    %只用於整型的計算,后一個數不能為0;
    fmod()可以對浮點型數據進行取模運算,后一個數可以為0,返回NaN(NaN,是Not a Number的縮寫,用於處理計算中出現的錯誤情況,比如 0.0 除以 0.0 或者求負數的平方根)。

  • 對於超出unsigned long long范圍不是很多的數,可以嘗試用long double(取模可以用fmod,注意精度損失,cout默認只保留6位有效數字,應當使用printf("%LF"),F小寫和大寫沒影響,但是 l 必須大寫成L,如果"%LF"無輸出,則只好強轉double保留15位有效數字)或兩個unsigned long long代替高精度

  • lcm為防止溢出,不可先乘后除,而且為保證精度應盡量整除

  • 四舍五入可以用<cmath>中的round函數(保留幾位小數可以先乘10的冪再round再除以10的冪)也可以用%.0lf輸出,若強轉int的話小數部分會直接舍棄

STL

  • 非C++11可以用map,但是使用unordered_map(數據存儲無序,不保證與插入順序一致)需要#include<tr1/unordered_map和using namespace tr1;
    unordered_multimap,unordered_set和unordered_multiset同理

  • set的insert不支持返回迭代器,需要二分查找,但multiset可以

typedef multiset<int>::iterator iter;
iter it=s.insert(x);//插入x,並返回x在s中的位置(迭代器)
  • set專有的s.lower_bound(x)比lower_bound(s.begin(), s.end(), x)會更快

  • 對於結構體/pair 賦值,a={x,y}是C++11開始才有的語法
    舊版本可以這樣寫:
    1.轉化為pair類型
    a=(pair<int,int>){x,y};
    2.調用pair的構造函數
    a=pair<int,int>(x,y);
    3.使用STL中的make_pair函數
    a=make_pair(x,y);

  • iostream包含<string>和<utility>(可以使用pair類型)頭文件,包含swap,max,min,getline(接收一行字符串,忽略回車符。),不包含freopen(只在 庫中)

  • end()指向的是最后一個元素的下一個位置,back()返回的是最后一個元素.類比字符串,end( )返回的是’\0’,back( )返回的是字符串的最后一個字符

優化技巧

  • 越在內層循環的數組維度在定義數組時越靠后,能提高運行效率(在連續的存儲空間內)(例如ST表的數組)

  • ST表建議預處理log函數省時間,for (int i = 2 ; i <= N ; i++) lg[i] = lg[i >> 1] + 1;
    對於st[i][j]=f(st[i][j-1],st[i+(1<<j-1)][j-1]),我們將i,j的順序調換一下可以提高效率

  • noip可以用exit(0)和goto,但是請謹慎使用。goto 語句可用於跳出深嵌套循環,可以往后跳,也可以往前跳,且一直往后執行。goto只能在函數體內跳轉,不能跳到函數體外的函數。即goto有局部作用域,需要在同一個棧內。

  • 萬能頭 include<bits/stdc++.h>(注意有些比賽可能不允許使用) 會增加編譯時間,但是比賽評測程序的時間限制指的是運行時間限制,而不是編譯時間限制,所以包含萬能頭文件不會影響到評分。

  • memset使用時要小心,在給char以外的數組賦值時,一般初始化為0或者-1或者0x3f或者-0x3f。
    memset -1其實是賦值為NaN,NaN在任何比較運算都返回false

  • CLOCKS_PER_SEC可以返回1s內clock()對應值,注意clock()運算速度慢,可以每多少次循環執行一次clock()

  • iostream庫中有rand()可以產生0~0x7fff(32367)的隨機數,初始化種子srand((unsigned int)time(0))(使用time函數獲取系統時間,得到的值是一個時間戳,即從1970年1月1日0點到現在時間的秒數,然后將得到的time_t類型數據轉化為(unsigned int)的數

  • 建議一次性寫好常用頭文件,這樣就不用寫到一半再補加,打斷思路)

  • 分配空間比較慢,用static可以防止每次重新分配空間

解題技巧

  • 編程:思維(數學),編程能力(實踐,即手速【非常重要,需要自己練】+正確率)

  • 解題注意嘗試逆向思維

  • 別忘了打表也是一種頗有妙用的算法

  • noip一定要把暴力分部分分拿足

  • 看到題目可以先考慮暴力模擬的方法,理解題意,理清思路,然后再根據理想的時間復雜度找到復雜度的瓶頸,對代碼各步不斷進行優化直到滿足要求,注意在空間足夠的情況下最好不要為省空間而壓縮數組減少變量,在時間復雜度足夠的情況下不要使用太多不一定正確而又復雜的優化,碼風應清晰,不要壓行,盡可能防止自己出錯

  • 考試注意事項:
    保持心態,可以帶口香糖之類食物緩解壓力,簡單題要看清題意先做,很久沒有思路就要想方設法寫暴力

  • 在做題前把細節想清楚,怎么寫比較簡便

  • 對於較大數據范圍,如果自己的做法會超時,當數據比較隨機時可以嘗試trick:減少操作次數,比如人為限定循環執行的次數以保證不超時,期望能得到正確答案

  • 對於多個變量的數據范圍,可以嘗試從小的數據范圍找突破口

  • 對於O(N ^ 2)優化為O(N):嘗試能不能從多層循環枚舉多個變量 改為 (使用一些數組存儲答案)只枚舉一個變量
    例如要枚舉左右邊界,可以考慮通過記錄前綴和、記錄最后一個合法左邊界的位置等方法,使得只需枚舉右邊界即可解出答案

  • 注意部分分,當題目有多個條件時可以考慮滿足其中部分條件的做法,再嘗試添加一個條件的做法應該如何改進

  • 想學好算法,要提高打代碼的速度
    想學好數學,要提高寫字速度,多練習多計算,提高計算能力與熟練度,尤其是高中生
    ——yxc
    暴力強解很重要,與其去想很多技巧,不如去強解,因為強解很快,計算能力足夠強就可以掩蓋掉很多劣勢,技巧跳躍性強,發揮不穩定,不容易想到,而暴力可以規避掉這些風險
    ——yxc

其它

  • 在把數組作為參數傳遞給函數時,函數的數組參數相當於指向該數組第一個元素的指針,所以不可以通過sizeof運算符得到函數數組的大小,直接用數組大小 * 字節數即可。而對於全局變量和局部變量(如函數內新建的數組)可以直接用sizeof

  • strict weak ordering
    stl的相關容器要求嚴格弱序。
    即 a>b 和 b>a 不能同時為真。
    stl判斷等價用的是 !(a<b) && !(b<a)
    所以重載<運算符的時候應判斷<,不可判斷<=

  • 矩陣乘法具有結合律不具有交換律
    具有結合律的問題可以嘗試倍增優化

  • y1, next, prev, has, hash在非c++11中可能會與保留字沖突

  • NP問題常識

  • noip已將棧空間開至內存空間的大小,不需擔心爆棧的問題

  • 對於二維數組數組名作為形參,函數形參聲明一定要至少給出第二個維度的大小

NOIP

應特別注意ios::sync_with_stdio(false) 無輸出的錯位輸出的比賽常見錯誤

普及組
模擬1: 2004不高興的津津, 2004花生采集,2005陶陶摘蘋果, 2005校i ]外的樹
模擬2: 2010按水問題, 2012刀寶, 2016買鉛筆, 2018標題統計
枚舉1: 2010數字統計, 2010導彈攔截2012質因數分解, 2013計數問題
枚舉2: 2014珠心算測驗, 2014比例簡化,2015掃雷游戲,2016回文日期
字符串處理: 2003兵 乓球,2008ISBN號碼,2008立體圖,2009多項式輸出
排序: 2006明明的隨機數, 2007獎學金, 2009分數線划定, 2011瑞土輪
數學: 2003麥森數, 2005循環, 2006數列, 2016魔法陣
貪心: 2004火星 人2007紀念品分組, 2008排座椅, 2015推銷員
DP1:
2003數字游戲,2006開心的金明,2007守望者的逃離,2014子矩陣
DP2:
2008傳球游戲,2009道路游戲, 2011表達式的值,2012擺花
提高組
模擬: 2003偵探推理, 20104機器翻譯, 2015神奇的幻方,2017時間復雜度
二分: 2010關押罪犯 201 1聰明的質檢員2012借教室, 2015跳石頭
數學: 2009Hankson的計算題, 201 1計算系數, 2016組合數問題, 2017小凱的疑惑
貪心: 2004合並果子, 201 1觀光公交2012國王游戲, 2013積木大賽
DFS: 2003傳染病控制, 2009靶形數獨,201 1Mayan游戲, 2015斗地主
樹: 2007樹網的核, 2014聯合權值,2015運輸計划, 2018旅行
圖論: 2003神經網絡, 2008雙棧排序, 2009最優貿易, 2015信息傳遞
DP1: 2003加分二叉樹, 2004合唱隊形, 2005過河,2006金明的預算方案
DP2: 2007矩陣取數游戲, 2008傳紙條,2010烏龜棋2015子串
DP3: 2016憤怒的小鳥, 2016換教室, 2017寶藏2018保衛王國

  • 好東西:

♥ 我的QQ音樂寶藏歌單 ♥ (強烈安利qwq~

OIerDb選手成績資料庫
graph圖論繪圖工具
標准指法練習TT(不一定適合打代碼)安裝包(打代碼按鍵方式建議根據個人習慣喜好)
excel轉換markdown 表格制作軟件:Typora
markdown latex表格在線生成
sm.ms在線圖庫
geogebra數學繪圖工具
numberempire數學計算器
oeis數列大全
mikutap音樂游戲
euclidea尺規作圖游戲
在線PS
網頁視頻


$ {\color {hotpink} {\Large \mathbf{{♥\ NOIP\ 2021\ RP++\ ♥}}} }$

360截圖20210809215734253.jpg
從2021-07-15到2021-08-09整天都在看基礎課提高課視頻,暑假爆肝了26天終於看完視頻並完成打卡,最后一次noip,希望能竭盡全力吧!

y總經典語錄收藏


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM