[toc]
一、常見錯誤
代碼細節
- 當兩個特別大的數相乘后取模時,要使用快速乘。
- 注意:使用long long時,要檢查傳參是否傳int。
- 注意:不要3數連乘 不要int×int 不要忘記負數 不要忘模 (long long范圍)。
- 數組大小,乘2,乘4等問題。
- 數組類型,連續定義有時會錯(如把int定義成bool)。
- 不要忘記初始化。
- 多測清空問題:每定義一個變量,數組,就要添加對應的清空代碼。
- 行,列一定要看清楚,不要寫混。
- 除法,逆元可能被卡(雖然模數是質數,但除數模完后,可能是0)。要盡量避免求逆元。對於上述情況,可以定義一個結構體維護,額外記錄下0的個數。
- 不能直接把字符串(char*)作為stl容器的類型,可以轉換為string或手寫比較函數,否則會錯。
其它
2-SAT:a->b即!b->!a 兩條邊都要連。
splay,lct注意事項:
旋轉時fa的連接。
旋轉時新根的fa。
旋轉時舊根的fa的兒子要賦值為新根。
維護標記:
旋轉后,2次上傳 splay前,下放標記 access更換重兒子時,上傳 cut后,上傳(也許)
行列式,高斯消元等的乘法值要預處理出來,否則會錯,就是
int t=1ll*sz[j][i]*ksm(sz[i][i],md-2)%md;
線段樹合並,主席樹的空間要略大於$O(nlogn)$。
線段樹合並要特判葉子節點。
a/b 向上取整 (a+b-1)/b 要特判負數。
注意輸出-0.00的問題。
使用標記永久化,上傳時要注意考慮標記。
主席樹在新建節點時要把原來的復制全。
點在多邊形內部判定,需要考慮點在邊界上的情況。
在樹dfs時,如果需要的臨時變量是公用的,那么需要先進行所有子樹的dfs,防止正在使用的這個變量被子樹破壞(通常是樹形背包)。
二、一些技巧
一、動態規划
DP設計
互質數的選擇,當質因數較大時采用分組背包,較小時狀壓記錄,以平方根為界(luogu壽司晚宴)。
對於狀態轉移有環的動態規划,使用如下方式求解:
若轉移里沒有max/min,只有加減乘除運算,可以建立方程組,通過高斯消元在O(n^3)得出解。
若轉移只是對一些元素取max/min(或第k大/小值),再加/減一個值,可以建圖,使用dij或spfa求解。
若u依賴v,且若v能更新u,則dp[u]>=dp[v],且求的是min(例如dp(u)=min(S(u)+∑dp[v],K(u)))。
每次:
1:把值最小的點標記 2:if 一個點u依賴的所有v都被標記 計算dp(u),並標記u, 3:回到1 直到所有點被標記。 就結束。
若2,3都不滿足,可以使用如下方法: 先把每個點都入隊,每次從隊首取出u,並計算dp(u),若dp(u)發生更新,則把依賴u的v都加入隊列(前提是v不在隊列中)。 這個方法很像spfa,但時間復雜度最壞為O(nm)。
例題:模擬賽tiger,luogu 騎士游戲
每次找一段刪除,刪除后自動合並的DP的一種解決方法:
先設dp(i,j)表示考慮i~j的結果 然后,考慮i這個點的情況:i一定是和之后的幾段一起合並后刪除 例如:i j -----*****--@@-&&&&&-------- 而中間的幾段一定不能一起刪除
因此,像 ------####------#####-------
這種情況是不可能的 所以,中間的段就是單獨的子問題了。 還要加一組狀態k,表示之前的連續段的信息。
對於樹形背包,通常,我們有兩種方法:
基於dfs合並,可以知道每個點的確切情況(比如子樹中選了多少),但是,復雜度較高(因為合並背包很慢)。 在dfs序上DP,復雜度較低,但是,不能 知道每個點的確切情況(因為是在序列上)。
有字典序限制的DP(以大於為例):
通常,我們有兩種方法: 1、記錄下當前是“放什么都大於(之前已經大於)”,還是“放大於的才大於”。 這個通常用於數位dp 但是,這個會使數有本質區別,難以處理。 2、確定一個前綴一模一樣,下一位大於,之后隨意。 這樣我們可以根據前綴算出當前狀態,並且之后的dp不受影響。 但是,有時這樣復雜度較高,並且,如果有多個有字典序限制,這個方法就不適用了。
若在樹上進行類似“隨機游走的”DP,可以不用高斯消元:
1、設$f_u$=\(k_u*f_{fa}+b_u\)。 2、由於u的計算僅依賴u的父親和兒子,把兒子用“1”的方法表示,兒子的父親是本身,這樣u就只依賴u和父親。 3、通過移項,使u僅依賴父親,進而表示為“1”中的形式。 4、最后,根節點的b就是答案。
有些容斥原理可以DP,在奇偶性改變時,取相反數即可。
關於連通圖計數的DP,可以設$dp_i$表示$i$個點連通的方案數,轉移時先算出方案總數,再減去不連通的。不連通的可以枚舉編號最小的點所在連通塊的點數$j$,用$dp_i$減去$dp_j*x$,其中$x$是剩余的$i-j$個點的方案總數。當圖相對復雜(比如完全二分圖)時,可以增加一維狀態,同時也要多一層枚舉。
樹形的有依賴dp也可以分兩次dp,分別考慮向上和向下傳遞(即換根dp)。
dp套dp:通過外層dp,算出使某個問題的解為給定值的方案數等,其中這個問題需要用內層dp解決。 按照內層dp所需要的輸入順序進行外層dp,將內層dp轉移所需要的依賴(可以觀察填表的過程)通過狀壓記錄成狀態(可以預處理出這些狀態的轉移自動機),進行dp。
數位DP可以統計L到R之間的$f(x)$之和。要求f只和x的數位有關。若f本身就是遞推式,可以按照f的遞推順序進行數位DP,並設F表示在限制下f之和,並把f的轉移式子改成F的。
數位DP若需要記錄進位信息,可以從低位到高位進行dp。例題
有時,可以用兩種方式表達計數DP,根據這兩種表示法相等的,來列方程求解。
秩為x的矩陣計數可以DP,只要記錄當前的秩,轉移時考慮是否增加秩即可。
麻將題(橫三豎三的),可以dp,因為三個橫的就能變成三個豎的,因此橫的不會超過2個,可以壓進dp狀態。具體細節
區間DP通常有兩種轉移形式:從兩端刪除,或從中間分裂。
涉及到矩形計數,最大矩形等問題時,可以考慮懸線法,配合單調棧,枚舉一個數作為區間的最小值。
網格圖中的放置可以考慮插頭DP,就是記錄當前考慮的行上面每一列的信息。
記錄幾個變量的相等關系,可以使用最小表示法。
DP優化
- 對於有些DP,可以先設計出比較簡單的狀態后,去掉一些無用狀態,等價狀態,進行優化。(可以使用記憶化搜索)
- 帶權二分:若選擇dp等滿足凸性,可以用直線切割凸包,將選的代價修改,通過二分找到合適的位置。
- 在對DP進行卡常優化時,可以試試改成記憶化搜索,有時能快很多。
- 如果轉移時需要枚舉相交的區間,可以使用容斥,轉化為枚舉不相交的區間,這樣l和r就沒有聯系了,可以分開計算,從而減少一維。
- 決策單調性總結
- 對於一般的01背包,復雜度為$O(NM)$,但如果物品體積的范圍C不算大,可以按照C去依次計算,當考慮到體積為x的物品時,對於模x意義下相等的數,從小到大滿足決策單調性,使用分治,可以把復雜度降低至$O(NlogN+MClogM)$。
void dfs(int l,int r,int l2,int r2,int x,int s) { if(l>r)return; int m=(l+r)>>1,wz=0; for(int y=l2;y<=r2&&y<=m;y++) { if(m-y>=su[x].size()) continue; ll t=dp[x*y+s]+su[x][m-y]; if(t>jh[x*m+s]) jh[x*m+s]=t,wz=y; } dfs(l,m-1,l2,wz,x,s);dfs(m+1,r,wz,r2,x,s); } for(int i=1;i<=300;i++) { for(int s=0;s<i;s++) { int t=(k-s)/i; dfs(0,t,0,t,i,s); } for(int x=1;x<=k;x++) { dp[x]=jh[x]; jh[x]=0; } }
- 在dp轉移時如果需要卷積,但是和他卷積的數組差分后容易維護(比如$dp(i)=\Sigma dp(i-j)*j$),可以不用NTT,直接用前綴和維護。
- 解決LIS問題,如果序列很長,但序列具有相似性,且值域不大,也可以設$dp(x,y)$表示以x結尾,長度為y的LIS,然后按順序枚舉x,滾動數組進行更新。
- 對於遞推,如果需要區間詢問(比如詢問區間內不同子序列數目),可以把轉移寫成矩陣,然后算出前綴積和逆矩陣的前綴積。注意乘法的順序
二、字符串
- AC自動機算法在匹配時,在fail樹上的鏈將都被匹配,所以經常與樹上算法聯合使用(luogu P3796,luogu P2336喵星球上的點名)。
- 后綴自動機跳fail的注意事項 (壓縮字符串)。
- 處理類似重復子串的問題,可以考慮枚舉長度L,然后每隔L放一個關鍵點,並計算相鄰的關鍵點的lcp,lcs等信息。
- 擴展后綴數組:在trie樹上構建,使用倍增,類似普通SA。
- 處理回文的問題時,可以枚舉回文中心。為了減少細節,有時可以在每個字符后面加入$#$。
- 回文自動機(PAM)
- 前后添加的哈希,可以從中間開始,維護左邊的后綴,右面的前綴的(從左到右的)哈希值。詢問子串時拼接即可。或者維護懶標記。
三、數學
數論等
$O(1)$快速乘代碼:
ll ksc(ll a,ll b) { return (a*b-(ll)((long double)a/md*b)*md+md)%md; }
中國剩余定理,可以在計算時之算出前綴積(變算邊乘),復雜度可以從$O(n^2)$降到$O(n)$,在計算時調整下余數即可。如果需要高精維護,此法可以起到優化。
1到n中所有數的約數的約數個數和大概是$O(nlog^2n)$的。
帶變量x的組合數,可以把組合數拆開變成下降冪,用斯特林數+二項式定理表示出系數,從而轉化為關於x的多項式。
如果對實數的精度要求非常高,且運算不復雜,可以定義分數類解決。
BSGS算法在有多組詢問時,可以適當增加步長。
使用對數可以把冪變乘,把乘變加,把除變減。例如:\(y>\frac{k}{x^a}\),可以變為$log(y)>log(k)-a*log(x)$。
在一個數列中,從某個位置開始的所有前綴的gcd,只有$logm$種,可以維護。
有時,可以對矩陣乘法進行改造,並且,通常改造后會滿足結合律。
同余不等式可以使用遞歸求解。
計數
遇到用若干個數相加組合成一個數的問題,可以考慮同余最短路(NOIP2017 小凱的疑惑,墨墨的等式)。
平方和問題:考慮平方的實際意義 (二維枚舉,二維dp)。帶組合數的,也可以用類似做法。
倍增合並(有時可以使用fft加速合並),有時能比矩陣快速冪少一個m。
矩陣乘法兩種形式:
- 數列遞推,每次生成一個。
- 高維遞推優化,每次推進一層。
枚舉,算貢獻(相當於交換求和符號)。
兩種容斥原理:交集,並集。(都要考慮到)
關於匹配的容斥:若有被多次匹配的,就有沒被匹配的,按照沒被匹配的進行容斥。
有些計數類的遞推(如:\(dp(i,j)=dp(i,j-1)+dp(i+1,j+1)\)),可以畫出轉移的圖后,(通過扭轉等)轉移為網格圖路徑計數問題。
網格圖有限制的路徑計數:
- 只有一個限制(如三角形,j<=i),可以容斥,不合法的可以將起點或終點進行對稱。
- 有兩個限制(如平行四邊形中),還是容斥,但要考慮反復經過分界線的情況。如:A,ABA,B,BAB等應減去。AB,BA,ABAB,BABA以及不進行對稱等應加上。
int ans=F(n,n+m); int a=0,b=0;A(a,b);B(a,b); ans=(ans+jisuan1(a,b))%md; a=0;b=0;B(a,b);A(a,b); ans=(ans+jisuan2(a,b))%md; a=0;b=0;B(a,b); ans=(ans-jisuan1(a,b)+md)%md; a=0;b=0;A(a,b); ans=(ans-jisuan2(a,b)+md)%md;
網格圖中兩條不相交路徑計數,可以用總數減去相交的,相交的就是把交點后面的路徑交換。
多項式可以放進矩陣中,進行矩陣快速冪。
如果多項式的次數始終不是很多,也可以直接用點值法維護,在開始和結束時進行NTT即可。
FFT為了防止炸精,可以預處理出單位根,而不是直接乘。
for(int i=0;i<(h>>1);i++) ww[i]=cp(cos(2*pi*lx*i/h),sin(2*pi*lx*i/h));
在FFT時若有$ik$之類的項,可以轉化為$\frac{k(k+1)}{2}+\frac{i(i-1)}{2}-\frac{(k-i)(k-i+1)}{2}$,便於FFT。
若要求$\Sigma A_{i,j}×x_×x$的最值,可以對於每一個x,只看關於它的項,發現是一個二次函數,那么讓這個x等於對稱軸即可。這樣會得出n條方程,高消即可。
四、博弈
- 只要兩個子游戲沒有關聯了,就可以用sg異或了。
- 翻硬幣游戲,只要選的硬幣是反轉的硬幣中編號最大的即可。計算時可以把直接翻轉理解為添加。
- 階梯nim:每次取走若干個放到上一堆中。只要記錄奇數位置的異或即可。這個游戲也可以放到樹上進行,方法相同。
五、樹上問題
在一棵樹上統計子樹信息,如果子樹信息不能快速合並,可以采用dsu on tree算法(luogu 雨天的尾巴,回文路徑計數)。
樹鏈合並(深度和減去相鄰lca深度),暴力樹鏈合並,access是均攤的。
基環樹方法:將環提出,變為森林,對每棵樹計算,然后合並。
樹統計(效率):線段樹合並>啟發式合並=dsu on tree
m個節點的所有lca在O(m)級別,可以用虛樹。
給定樹上若干的點,求最小生成樹大小,可以按照dfs序排序后,用總深度減去相鄰的lca的深度,再減去所有的lca的深度。
建虛樹的步驟
int build(vector<int> &sz) { //1. 將點按照dfs序為關鍵字排序(此處已省略)。 for(int i=0;i<cs;i++) ve[cl[i]].clear(); cl[0]=st[0]=sz[0];cs=tp=1;//2. 清空數組,將第1個點壓到棧中。 for(int i=1;i<sz.size();i++) { int u=sz[i],lc=getlca(u,st[tp-1]);//3. 枚舉到下一個點u,計算u與棧頂點的最近公共祖先lca while(tp>=2&&de[lc]<=de[st[tp-2]])//4. 假設棧中棧頂下方的點為w(若棧中只有1個點就直跳過這一步),若w點的深度大於等於lca就把v向w連一條邊,並且彈掉v,重復此步,否則就到下一步。 { ve[st[tp-2]].push_back(st[tp-1]); tp-=1;lc=getlca(u,st[tp-1]); } if(lc!=st[tp-1])//5. 若lca不是當前的棧頂點,那么就把lca和棧頂點連邊 { ve[lc].push_back(st[tp-1]); st[tp-1]=cl[cs++]=lc;//並把棧頂變為lca } st[tp++]=cl[cs++]=u;//6. 最后把u壓入棧中 } for(int i=0;i+1<tp;i++)//7. 把棧頂v與棧頂下方的點為w連邊,並且把v彈掉,這么做直到棧里只有一個點 ve[st[i]].push_back(st[i+1]); return st[0];//這個點就是虛樹的根了 //為了方便,有時把1也加進去。 }
長鏈剖分優化dp時,對於輕兒子直接爆算,重兒子繼承后將指針移位,進行+1或-1的調整。比如:
int& F(int u,int i) { if(i>=dep[u]) return z0; return f[lf[u]+i]; } if(son[u]!=-1) { dfs2(son[u],u); lg[u]=lg[son[u]]+1;//指針移動 lf[u]=lf[son[u]]-1;//指針移動 } F(u,0)=1; if(son[u]==-1)return; for(int i=fr[u];i!=-1;i=ne[i]) { if(v[i]==fu||v[i]==son[u]) continue; //進行轉移 }
若要求一段路徑上的顏色數,可以考慮每種顏色的貢獻:若刪除所有這種顏色后,起點和終點不在一個連通塊中,則有貢獻。
樹的重心,也是樹上到其他點距離之和最小的點,滿足其子樹大小的最大值最小(不超過一半)。如果有兩個重心,則這兩個重心連着,且最大子樹正好為一半。
距離樹上某一個點的最遠點一定是(任意)直徑的一個端點。
合並兩棵樹的直徑時,新樹的直徑端點一定是這兩棵樹的直徑端點(共四個)中的兩個。可以分6種情況討論。
換根DP:先進行一次DFS,第二次DFS時維護父樹的信息。需要考慮如何快速計算一棵子樹挖去它的一個兒子后的DP值。
求樹上兩條路徑的交:兩條路徑(a,b)與(c,d),四個點兩兩求LCA,得到$x1=lca(a,c),x2=lca(a,d),x3=lca(b,c),x4=lca(b,d)$。再從這四個點中取出深度最大的兩個點p1,p2 若p1≠p2,則交為p1到p2;若p1=p2,且p1的深度小於 ca(a,b)或小於lca(c,d)的深度,則無交點,否則只有一個交點p1。
要求出樹上所有路徑/連通塊的信息,可以使用點分治,這樣,每個路徑/連通塊都會考慮到,且只需$O(nlogn)$。
$m$條路徑一定可以覆蓋有$2m$個葉子的樹。
六、圖論
最小生成樹更新:只需判斷環的最值即可
圖上的所有MST,滿足:
1、對於任意權值的邊,所有最小生成樹中這個權值的邊的數量是一定的(根據克魯斯卡爾算法即可得出) 2、對於任意正確加邊方案,加完小於某權值的所有邊后圖的連通性是一樣的 3、對於相同權值的邊,任意順序都是可以的。
邊權只有0,1的圖的最短路可以用bfs求解。
在涉及到連通性,以及最長路等問題要考慮縮點(受歡迎的牛)。
歐拉回路構造:出棧時輸出+“當前弧優化”
三元環枚舉:按度數將邊定向,大->小,之后暴力枚舉即可。
2-SAT:若a能推出b,連a->b,連!b->!a。
之后,找scc,若一個變量的兩種取值位於同一scc,則無解。 若要構造解,則取兩種取值中scc編號較小的。
奇環的判斷使用黑白染色。
給一個圖,刪邊,問連通性,可以把非樹邊隨機一個權值,樹邊的權值為覆蓋它的非樹邊的權值的異或和。查詢時求出是否有異或和為0的子集即可。
點權圖的最短路,每個點只會被更新一次,這個性質可以用於某些優化。比如。
點權都為正數的最小(非零)權閉合子圖,可以tarjan,然后考慮出度為0的SCC。
七、網絡流
平面圖的最小割就是對偶圖的最短路(每個面看做一個點,相鄰的面看做邊)(luogu P2046海拔,luogu 狼抓兔子)。
網絡流的最小割模型:
- “文理分科”
- “選擇+2個具有貢獻”
在題目涉及到所有點對的最大流/最小割時,可以通過最小割樹,將其轉化為樹鏈上的最小值。
對於二分圖完美匹配的判定,可以考慮Hall定理。
問題:有n個點,選擇每個點有代價,m條關系(x,y,z)表示同時選擇x,y有z的收益。使收益-代價最小。
可以使用最小割求解。 首先,假設可以得到所有收益,不付出代價。並用最小割割掉不合法的。 S向每個點連邊,邊權為代價。每個點向T連邊,邊權為包括它的收益和的一半。 對於每條關系,$x$到$y$連邊權為$\frac{2}$的雙向邊。 可以把邊權先擴大到2倍。
最小割的數學定義,令$x$為一個01變量,我們假設若$x$最后在$S$集合,則$x=0$,否則$x=1$ 則邊$(S,x,a)$對答案的貢獻為$ax$ 則邊$(x,T,a)$對答案的貢獻為$a(1-x)$ 則邊$(x,y,a)$對答案的貢獻為$a(1-x)y$ 同理,如果我們能把貢獻寫為以上的形式,則可以用最小割求最優解 比如:有若干個01變量,每個變量取0/1有代價,還有若干關系(x,y,z)表示如果x取1,y取0,有z的代價。要求總代價最小。可以使用最小割。
有些網絡流問題,可以先u建出一個比較簡單的圖,然后把點,邊合並。
八、數據結構
有時,權值線段樹可以代替treap,能減小常數和代碼量(luogu 郁悶的出納員)。
有時,線段樹可以代替splay,方法時記錄每個位置是否有元素,查詢第k個值時線段樹上二分(NOIP2017 列隊)。
處理區間內出現過的數時,可以求出每個數的后繼,然后就變成了區間內大於某數的數(luogu HH的項鏈)。如果在樹上可以使用這種方法
某種在線-->離線:建立線段樹/樹狀數組,當某個區間都被加入時,離線處理,要求詢問區間分裂后能合並結果(其實這個叫做二進制分組)。
很多樹形數據結構都可以打標記(線段樹,splay,左偏樹)。
通常的樹持久化方法:每次新建一個根。避免持久化的技巧:版本樹(離線)。
在處理覆蓋問題時,可以用並查集維護每個點向后第一個未被覆蓋的位置(包括自身),比線段樹快,樹上覆蓋也可以。
笛卡爾樹:將RMQ變為lca,可以動態維護單調棧,解決RMQ之和等問題。
計算最大矩形時,除了單調棧,還可以遞歸,就是每次找到最小值,並遞歸它兩邊的區域。
處理異或的問題有一種常用技巧:就是把每個二進制位拆開單獨處理,這樣只有不同的才會有貢獻。可以將異或問題轉換為了是否為不同的數。
有時,我們的可持久化並查集只需要查詢歷史版本,不需要在歷史版本上修改(例如添加邊后,詢問之前的連通性)。
這時,我們有一個log的做法。 維護一個只按秩合並,不路徑壓縮的並查集。 對於每條邊,記錄它出現的時間,判斷連通性找根時,如果它到它的父節點的邊出現了,就找父節點的根,否則它就是根。 這樣,不路徑壓縮,邊的狀態只有兩種,存在/不存在,若存在就是唯一的。
線段樹合並,通常用在樹上。
需要滿足的條件:在合並時,對於一棵空樹,可以直接return或在另一棵樹上打標記。
看到RMQ之和的問題,可以考慮單調棧,或者每次找RMQ並分治兩邊。
只進行加的高精,有時可以使用線段樹維護:加法時,在線段樹上二分進行進位。比較時,使用哈希找lcp進行比較(要注意從高位到低位)。並且,可以可持久化。
二維線段樹通常使用動態開點,x樹的每個節點都保存它的y樹的根。
主席樹,二維線段樹等數據結構如果要區間修改,通常使用標記永久化(要求標記之間沒有順序要求)。
trie字典樹支持刪除,只要額外維護子樹大小即可。
標記永久化可以解決一些其它問題,比如:有一棵樹,單點修改,查詢一個點到根的路徑上與x異或的最大值。這個可以做到$O(nlog^2n)$時間,$O(nlogn)$空間。
啟發式合並,啟發式分裂,有時可以做到log。
九、幾何
- 平面分治,可以用於平面上的最近距離等問題(最近點對,最小周長三角形)。只要先把x排序,分治時把y排序,用雙指針掃描即可。
十、其它
算法設計
生成匹配的括號序列,只需模擬一個棧,記錄棧中的括號數即可(CF1015F,CF508E)。
求第k小值(k不大),可以采用A*算法,要保證每種狀態都能被擴展出,且擴展出的狀態非遞減(luogu 超級鋼琴,K短路,OVOO)。
離線處理以及強制在線:
- 離線處理可以把詢問排序,並按照順序求解。強制在線可以全部處理后可持久化(NOI2018 歸程)。
- 離線處理可以倒推。
- 離線處理可以得出每種操作的時間序(loj121 動態圖的連通性)。
當處理一個要求整體滿足的問題時,可以只考慮局部,當所有的局部都滿足時,整體就滿足了(luogu 雙棧排序,NOI2018 冒泡排序)。
第k小問題的計數可以用容斥 (秘密襲擊),就是說:
x成為第k小需要滿足兩個條件:$<x$的數目小於k,$\leq x$的數目大於等於k。 可以用滿足1的減去滿足1且不滿足2的。 顯然,不滿足2,就一定滿足1。 因此,用“$<x$的數目小於k”減去“$\leq x$的數目小於k”即可。
meet-in-the-middle 算法
應用:BSGS優化,分別以1,平方根為步長,進行預處理,這樣,任意一個數都能用處理好的兩個數之和來表示。
易忘的:
線段樹合並 分塊,分類 差分約束 預處理 線段樹分治,線段樹建圖,線段樹掃描線 2-SAT bitset優化
看到方差可以考慮推式子,轉化為平方和-平均數×總和。
排列的三維偏序數,可以容斥。答案為$\frac{F(a,b)+F(a,c)+F(b,c)-\frac{n*(n-1)}{2}}{2}$。F為二位偏序。
判斷兩種狀態能否到達時,若狀態的轉移有單方向性,可以求出兩個狀態按照方向轉以后到達的最終狀態,判斷是否相等。(其實就是狀態的轉移是樹形的)。
正難則反,可以把問題反過來(比如:讓求字符串中是否有不相等的距離為k的兩個位置,由於字符串算法大多是求匹配的,所以可以反過來,看是否都相等)。
坐標轉化:曼哈頓距離是∆ x與∆ y之和,切比雪夫距離是∆ x與∆ y的最大值。
考慮一個問題時,可以考慮特殊情況的解法(比如樹上問題,可以先考慮鏈,菊花的情況;基環樹,可以先考慮樹上的情況)。
如果網絡流的建圖比較簡單,可以考慮模擬網絡流優化(數據結構實現增廣),或堆模擬反悔操作
分類考慮:一部分滿足種類數小(預處理),一部分計算快,可以分類求(通常平方根為界,但實際上有偏差)。
如果題目中要求最后一個,即max,可以使用min-max容斥變為求max,即第一個。
算法優化
傳有數組的結構體要盡量用引用。
模數要用define或const!!!!會快些
在枚舉子集時,若要遍歷其中的元素,可以采用如下方法,會快些:
for(int i=0,j=1;i<n;i++,j=(j<<1)) lg[j]=i; //…… for(int i=0;i<(1<<n);i++) { for(int t=i;t>0;t^=t&(-t)) { int k=lg[t&(-t)]; //…… } }
FWT的一個小技巧:此處
Hash掛鏈通常比map快,快很多,但要注意hash的方法。
在使用枚舉+二分時,可以把枚舉的順序打亂,並在二分前先判斷當前的最優解是否可行,這樣可以把二分的次數降到期望$O(logn)$。
在優化算法時,可以考慮把若干次相似的操作合並成一次進行。
在圖中路徑計數使用矩陣快速冪時,如果這個圖時DAG,可以按照拓撲序之進行三角的枚舉,從而把常數變為$\frac{1}{6}$。
若矩陣快速冪復雜度太高,可以考慮使用BM求出前幾項遞推式,進行快速遞推。
帶修改的二維偏序的枚舉,可以做到$O(nlogn)$,方法是按照第一維建立線段樹,維護第二維的最值。在枚舉時dfs線段樹,根據這個最值判斷。例題
三、一些公式
組合數
二項式反演
min/max容斥擴展
單位根反演
EXCRT
\(x = r1 (mod a1)\) \(x = r2 (mod a2)\) \(k1 * a1 + r1 = k2 * a2 + r2\) \(k1 * a1 = (r2 – r1) (mod a2) (3)\) 在此基礎上我們求k1這個未知變量即形如$ax = c (mod b)$中的x的解, 設得出的解為k0(最小正整數解), 那么(3)的 k1的通解一定為 \(k1 = k0 + t * \frac{a2}{gcd(a1, a2)}\) (t為任意正整數) 帶入(1)中即可得$x = k0 * a1 + r1 + t * a1 * \frac{gcd(a1, a2)}$ \(x = k0 * a1 + r1 (mod (lcm(a1, a2))) (4)\) (4)就是我們想要的合並方程(切記k0為最小正整數解)
杜教篩
\(S(n)=\Sigma(f*g)(i)-\Sigma g(d)*S(\frac{n}{d})\)
四、一些模板
由於篇幅太長,見此處