目錄
0.前言
算法這種東西,有時候其實並不管用,還是一波暴力走人,退役。。。。。。乾脆。
一.動態規劃——必考必考必考!!!
DP這種算法,不是說了概念就能懂得,只有身經百戰,然後靠一波運氣纔行。下面有一些常見的DP考法:
1.揹包
這種DP方式很常見,並且範圍很廣,而且有較明顯的方法提示。就是在題目有求和的最大的限制,並且給你一個數列的時候,肯定是揹包,說都不用說。還有個更明顯的標誌:每個數及其和不大!這裏有兩種常考揹包:
(1)01揹包
for (int i = 1; i <= n; i ++){//n表示物品種數
for (int j = m; j >= w[i]; j --){//m表示揹包容量
dp[j] = max (dp[j], dp[j - w[i]] + v[i]);//w表示每種物品的體積大小,v則表示價值
}
}
for (int i = 1; i <= m; i ++)
ans = max (dp[i], ans);
(2)完全揹包
for (int i = 1; i <= n; i ++){//各變量含義同上
for (int j = w[i]; j <= m; j ++){//與01揹包反過來
dp[j] = max (dp[j], dp[j - w[i]] + v[i]);
}
}
for (int i = 1; i <= m; i ++)
ans = max (dp[i], ans);
但是不能完全靠這板子去得分,題目經常都很靈活,但是隻要稍微改動一下就行了。比如:讓揹包去存已經用的物品個數,因爲題目限制了物品個數。
2.線性DP
這種DP就包括最長公共上升子序列,最長上升(下降)子序列,單調隊列,背一下,這種題目挺好做的。
3.多維DP
這是最難想的DP類型的題目,通常在一個矩陣的背景下進行,這種特別難,只有多花時間想一想,有幾個經典的類型:
1.最大子矩陣:一行一行的往下轉移
2.創意喫魚法:一層一層的往左和往下擴展
總而言之,從一個下標轉移到下一個下標,或從一排轉移到下一排的轉移方式,是DP的精髓。
DP優化我就不想說了,比如斜率優化、平行四邊形優化根本就不是我的料。
二.貪心
貪心和動態規劃容易搞混,但是記住,需要狀態轉移,有多種情況的就是動態規劃,所謂多種情況,就是你貪心考慮不到。
貪心的精髓就是貪。所以貪心經常需要排序,這有點玄學,反正我有點懵,憑着感覺去貪就行了。
三.模擬
我認爲,這個就不叫算法,其實就考察一個東西:細心
四.圖論——很靈活
這個算法不僅內容多,而且考的非常靈活,經常讓你手足無措,最後看了題解才發現就一個最小生成樹(舉個例子)而已。
這裏先上模板:
1.最短路
分spfa和Dijkstra(floyd就不說了)
(1)spfa
這個我就不想說了
(2)Dijkstra(+堆優化)時間複雜度遠快於spfa
注意,Dijkstra是不能有負環的,是有spfa才能判環(若一個點進入n+1次,就存在環)
2.最小生成樹
直接上了:
bool cmp (int x, int y){
return w[x] < w[y];
}
void makeSet (int x){
for (int i = 1; i <= x; i ++)
fa[i] = i;
}
int findSet (int x){
if (x != fa[x])
fa[x] = findSet (fa[x]);
return fa[x];
}
void unionSet (){
for (int i = 1; i <= k; i ++){
int e = r[i], U = findSet (u[e]), V = findSet (v[e]);//特別注意是u[e]而不是u[i],我曾經錯過
if (U != V){
ans += w[e];
fa[U] = V;
Sum ++;
}
}
}
int main (){
scanf ("%d %d", &n, &m);
makeSet (n);
for (int i = 1; i <= m; i ++){
k ++;
scanf ("%d %d %d", &u[k], &v[k], &w[k]);
k ++;
u[k] = v[k - 1];
v[k] = u[k - 1];
w[k] = w[k - 1];
}
for (int i = 1; i <= k; i ++)
r[i] = i;
sort (r + 1, r + 1 + k, cmp);
unionSet ();
}
3.強連通分量
這個算法你不能打錯任何一個字母,因爲一點細微的差錯可能導致全盤崩潰。但是這個算法考的可能性不大
(1)有向圖(含割點+縮點)
void Tarjan (int u){//求強連通分量
DFN[u] = LOW[u] = ++ now;
instack[u] = 1;
s.push (u);
for (int i = 0; i < G[u].size (); i ++){
int v = G[u][i];
if (! DFN[v]){
Tarjan (v);
LOW[u] = min (LOW[v], LOW[u]);
}
else if (instack[v]){
LOW[u] = min (DFN[v], LOW[u]);
}
}
if (DFN[u] == LOW[u]){
num ++;
int v;
do {
v = s.top ();
s.pop ();
instack[v] = 0;
}while (u != v);
}
}
(2)無向圖(含割點)
void Tarjan (int x, int fa){
dfn[x] = low[x] = ++ cntd;
int children = 0;
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (! dfn[tmp]){
children ++;
Tarjan (tmp, x);
if (low[tmp] >= dfn[x])
isgedian[x] = 1;
low[x] = min (low[x], low[tmp]);
}
else if (dfn[x] > dfn[tmp] && fa != tmp){//注意fa!=tmp
low[x] = min (low[x], dfn[tmp]);
}
}
if (fa < 0 && children == 1)//注意
isgedian[x] = 0;
}
(3)無向圖求每個連通分量
void DFS (int x){
vis[x] = num;
sum ++;
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (isgedian[tmp] && vis[tmp] != num){//注意vis[tmp]!=num
cut ++;
vis[tmp] = num;
}
else if (! vis[tmp])
DFS (tmp);
}
}
4.二分圖匹配
本蒟蒻實在太弱,不會模板,但一定要了解它的意思,概念。
四.數論
這個算法對於我來說就是學了也不會,因爲只會板子,做題怎麼也做不起,我太難了。但是有幾個必背的板:
1.逆元
rfac[0] = rfac[1] = 1;//rfac表示逆元,如果空間不滿足,用qkpow暴力求
for (int i = 2; i <= MAXN; i ++)
rfac[i] = rfac[mod % i] * (mod - mod / i) % mod;
2.組合數
//法一:特別快
int C (int m, int n){
return fac[m] * rfac[n] % mod * rfac[m - n] % mod;
}
//法二:基礎
int C (int m, int n){
if (n > m / 2)
n = m - n;
int ans = 1;
for (int i = 1; i <= n; i ++){
ans = ans * (m - i + 1) / (n - i + 1);
}
return ans;
}
優化:Lucas定理
int Lucas (int n, int m){
if (n == m || !m)
return 1;
return C (m % p, n % p) * Lucas (m / p, n / p) % p;
}
常用處:楊輝三角
遞推式:
3.歐拉函數
int phi (int x){
int res = x;
for (int i = 1; i * i <= x; i ++){
if (x % i == 0){
res = res / i * (i - 1);
while (x % i == 0)
x /= i;
}
}
if (x > 1)
res = res / x * (x - 1);
return res;
}
相關定理:
(1)歐拉定理
(a與p互質)
(2)指數循環節
常用於指數太大時。
(可一直遞推下去)
4.擴展歐幾里得——求解二元一次方程組
int exgcd (int a, int b, int &x, int &y){
if (!b){
x = 1, y = 0;
return a;
}
int r = exgcd (b, a % b, y, x);
y -= a / b * x;//核心關鍵
return r;
}
5.中國剩餘定理(CRT)
//b[i]是模數,p[i]是同於是右邊的ai,M是所有模數的公倍數
void exgcd (int a, int b, int &x, int &y){
if (! b){
x = 1;
y = 0;
return ;
}
exgcd (b, a % b, y, x);
y -= a / b * x;
}
int main (){
int ans = 0;
int x, y;
for (int i = 1; i <= n; i ++){
int tp = M / b[i];
exgcd (tp, b[i], x, y);
ans = (ans + p[i] * tp * x) % M;
}
五.搜索
這種算法,相信都家喻戶曉。但是,同樣是爆搜,人與人做出來卻不一樣。有的人只能卡個暴力分,而有的人他就A了,連DP題目他都能用搜索過掉,這就是搜索技巧的問題了(隆重爲大家推薦一個大佬,曾經用搜索過掉了分組揹包,驚呆了(點擊:孫子))。下面隆重介紹兩個在考試時能夠發揮神威的技巧:
1.記憶化
只要空間允許,這種方法就能省掉重複計算的過程,快得一批。
2.剪枝——最重要的技巧
剪枝十分重要,但是十分難想,甚至有時後很玄學,但是這裏有幾個常見的技巧:
(1)如果當前的答案已經不比已經得出的答案優,就直接回溯;
(2)如果當前的值不行,就不要去試後面重複的值,通常附帶一個排序。
3.看範圍答題
這是最有趣的,大家要有想象力,說不定搜一半,再DP一般就過了呢?或者。。。。。。
典例:
但是重點在於:看數據範圍,數據大的稍暴力,反之DP或優化。額,有點騙分的樣子。。。。。。