2023 國慶CSP-J刷題營筆記整合

dienter發表於2024-08-07

Day1

列舉與搜尋


列舉

例1:洛谷P1008

  • 考慮列舉所有三位數,可過,然而時間複雜度高,容易列舉沒寫不符合題目的答案
  • 可找到1:2:3條件來列舉

例2:洛谷P1217

  • 列舉a-b中所有迴文質數,TLE
  • 列舉a-b中所有質數,TLE*2
  • 列舉迴文數(正解)
列舉迴文數:
1. 列舉前一半(總結規律可以發現如果為質數第一位只能為1 3 7 9)
2. 判斷位數奇偶
	eg:
	列舉了前一半137
	奇數:137731
	偶數:13731
  • 記錄數量,AC

例3:洛谷P1102
雙指標演算法

感覺類似莫隊,就是不斷縮小範圍直到最小區間

#include<bits/stdc++.h>
using namespace std;
#define int long long
int ans;
signed main(){
	int a[1000050],n;
	a[0]=-10000000;
	int vr,vl;
	cin>>n>>vl;
	vr=vl;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+n+1);
	int j1=0,j0=0;
	for(int i=1;i<=n;i++){
		
		while(a[i]-a[j0]>vr) j0++;
		while(a[i]-a[j1]>=vl) j1++;
		if(j0<j1){
			ans+=j1-j0;
			//cout<<j1<<' '<<j0<<"\n";
		}
	}
	cout<<ans;
	return 0;
}
/*
4 1 1
1 1 2 3 
*/

搜尋

搜尋本質也是列舉,差別在於程式碼實現

全排列模型
  • DFS列舉全排列
  • STL大法好algorithm庫的next_permutation(p+1,p+n+1)函式,且當p為字典序最大的排列時,它會返回0,否則會返回1

兩種方法各有優劣,前者方便減枝,後者實現更為方便。

例1:洛谷P1219

  • 列舉全排列,結束

二進位制模型、集合/正整數劃分模型.......

剪枝

  1. 變換搜尋順序洛谷P1074
  2. 可行性剪枝
  3. 物品排序後搜尋(沒有什麼道理,卻能使搜尋變得十分優秀。)
  4. 最優性剪枝(01揹包問題,但是DFS)


Day2

資料結構

連結串列:

單向連結串列:

模版:洛谷B3631

前項星:

本質:前項星是由多個單項鍊表組成,維護鏈頭陣列,然後可以支援每個點加邊。

struct nod
{
	int next,to;
}e[xx*2];
int cnt,h[xx];
void add(int x,int y){
	cnt++;
	e[cnt]={h[×],y};
	h[×]=cnt;
}
void dfs(int x,int y){
	for(int i=h[×];i;i=e[i].next)
		dfs(e[i].to,x);
}

洛谷P3916


雙向連結串列:

模版:洛谷P1160

就比單向連結串列多了個後繼而已=_=


之所以重要,是因為它其實就是遞迴的邏輯結構,先進後出

STL:

stack<int>stk;
stk.push(1);
stk.pop();
cout<<stk.top()<<"\n";
cout<<stk.size()<<"\n";

洛谷B3614

單調棧

  • 始終保持站內元素為單調的
  • 比較下標優先
輸入序列:1 4 2 3 5
棧內變化:
5
5 3
5 3 2
5 4
5 4 1

用來求第一個大於某個元素的下標

洛谷P5788


動態陣列vector

顧名思義,它的容量是變化的,是動態的陣列

當內部使用的容量到達了它的上界,他的容量就會翻2倍。

如:

a={}          a.push_back(1)  0
a={1}         a.push_back(2)  1
a={1,2}       a.push_back(3)  2
a={1,2,3}     a.push_back(4)  4
a={1,2,3,4}   a.push_back(5)  8
a={1,2,3,4,5}                 16
     ...            ...

我們也可以分配記憶體a.resize()

也可以釋放記憶體vector<int>().swap(a),預設是佔24位元組


佇列

就是模擬現實中的排隊的佇列,先進先出

洛谷B3616

雙端佇列(deque)

就是兩個頭相對的佇列合一塊,STL是支援隨機訪問

然而空間是特別大的,預設分配記憶體有80個位元組,1e6個記憶體總共80多MB

單調佇列

洛谷P1886

原理跟單調棧類似,就是可以取隊首元素可pop隊首了


一種完全二叉樹,可以維護一個最小值或最大值,插入數值時間複雜度為\(O(log_2n\)),刪除同

洛谷P3378

對頂堆

一個序列,我們每次加入一個元素,或者進行詢問。

維護一個初始指標i=0,每次詢問的時候將i=i+1然後詢問第i小的值是多少。

洛谷P1801

哈夫曼樹

洛谷P1090

  • 每個數貢獻的次數是他到根的邊數。
  • 數大的貢獻較少,即經過的邊數較少。
  • 如果把邊順次標號為 0,1,則每個葉子到根的一個字串稱為哈夫曼編
    碼。


Day3

動態規劃

簡單DP

洛谷P1216

1.正著遞推

  • 可以從頂往下推
  • f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j];

2.倒著遞推

  • 從下往上推
  • f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j];

揹包DP

給定n個價值為\(V_i\),體積為\(W_i\)的物品,你有一個容積為M的揹包,求怎樣裝物品才能使價值之和最大。

採藥

洛谷P5664

洛谷P5665

區間DP

洛谷P1430

  • f[l][r]表示還剩下[l,r]的數,我們先手獲得的最大價值==後手獲得的最大價值
  • 可以直接深搜


Day4

圖論

概念

樹:

  • 無環
  • 連通
  • U,V之間唯一一條路

有向無環圖(DAG):

  • 可以進行拓撲排序
  • 可以進行DP
  • 都為二分圖

樹的儲存:

  • 記錄每個點的父親
  • 每個點存他的兒子
  • 當成圖來存

樹的名詞:

  • 子樹
  • 深度
  • 路徑
  • 直徑:距離最大的一對點之間的距離為多少
  • 重心:以一個點為根,左子樹的點和右子樹的相等則稱該點為一棵樹的重心

二叉樹

  • 每個點最多有兩個兒子
  • 二叉樹的先序遍歷為它的DFS順序

完美二叉樹:每個點都有兩個兒子

完全二叉樹:點n的父親為n/2,點的兒子為2n、2n+1


DAG最短/長路

直接BFS即可

序:

  • 傳遞性(a>=b,b>=c,a>=c)
  • 自反性(a<=b,b<=a,a=b)
  • 反對稱性(a+b=c,c+b≠a)

正序:所有序都可比較

偏序:不是所有序都可比較,但是滿足序的三個性質

DAG所有點預設都是偏序

二分圖

染色(把一個圖染成黑白兩色,讓著圖邊兩端顏色都不同):只需BFS一遍即可



Day5

貪心

就是區域性最優解,看哪個最好選哪個

正確性證明:

  1. 數學歸納法

例1:

  • 定義一個函式f[i]表示取i個線段,最右端最小是多少
  • 變換過程中最優解不變

例2:

  • f[i]:1~i時間前有多少教師
  • g[i]:1~i時間最多的同時講座的數量
  • h[i]=i時間同時講座的數量
  • f[0]=0=g[0]
  • 往i區間裡放,f[i-1]=g[i-1]>=h[i-1]
  • 能放得進去,f[i-1]=f[i],g[i-1]=g[i]>h[i]
  • ∴f[n]=g[n]

2.歸納法

3.交換論證

  • 任何最優解都可以可以變成貪心的最優解,
  • 變換過程中最優解不變

例題:國王遊戲


數學

矩陣

矩陣乘法

省略

矩陣加速遞推

  • f1=f2=0
  • \(f_n=7f_{n-1}+6f_{n-2}+5n+4×3^n\)
  • \(f_n\)

高斯消元

特點:

  • 兩方程互換,解不變;
  • 一方程乘以非零數 k,解不變;
  • 一方程乘以數 k 加上另一方程,解不變。

例:

\(\begin{cases} 7x_1+8x_2+9x_3=13 \\4x_1+5x_2+6x_3=12 \\x_1+2x_2+3x_3=11 \end{cases}\)

  • 7 8 9==13 → 1 0 04
  • 4 5 6==12 → 0 1 05
  • 1 2 3==11 → 0 0 16

取模(mod)

\(100 \equiv1\mod {9}\)

\(\equiv\):模運算下的等價

逆元

定義

  • 假設p是一個質數,且\((a, p)=1\),\(a*b=1(mod p)求b\)
  • b類似於a的倒數?
  • 也就是說,乘上—個b和除以一個a的效果是一樣的b稱作a的逆元,記作\(b=a^{-1}\)
  • \(c/a=c*a^{-1}(\mod p)\)

  • 費馬小定理
  • 如果p為質數,且(a,p)=1;
  • \(a^{p-1}\equiv(1\mod{9})\)

其他

埃拉託斯特尼篩法

  • 求1~n所有素數
  • O(nlogn)
  • 從小到大列舉數把他的倍數篩去

卡特蘭數:

1,1,2,5,14

公式:

\(H_n=\begin{pmatrix}2n\\n\\\end{pmatrix}-\begin{pmatrix}2n\\n-1\\\end{pmatrix}\)

  • 只要讓你輸出單個數字的題,暴力程式發現前幾個結果為上面的數列
  • 可以試著直接輸出卡特蘭數。


Day6

分治

  • 分而治之,就是把一個複雜問題分解為多個簡單的子問題來解決
  • 限制為複雜問題要可分

應用:

歸併排序:

基本流程:

sort(l,r);
mid
sort(l,mid);
sort(mid+1,r);
marge(l,mid,r);

逆序對

基本流程:

solve(l,r);
mid
ans+=solve(l,mid);
ans+=solve(mid+1,r);
void solve:
	a    b
	a[i]>b[j]

區間max之和、最大欄位和....

時間複雜度:

最大欄位和:T(n)=2T(n/2)+O(n)=O(nlogn)

區間max之和:T(n)=2T(n/2)+O(1)

例題:
洛谷P1966


二分

二分查詢

簡介

  • 一種查詢陣列元素的方法
  • 陣列必須有序
  • 時間複雜度O(log(n))

STL

  • lower_bound:找到陣列中第一個大於等於x的下標,返回一個地址
  • upper_bound:用法同lower_bound
  • binary_search:二分查詢一個元素,返回bool值

板子:洛谷P2249

例題:洛谷P1102

  • 可以使用第一天的雙指標演算法

  • 排序後也可以二分查詢


二分答案

簡介:

  • 對答案進行二分查詢

  • 要有一個明確的(答案)界限,如2 1 3 5 6可以二分(界限為4)

例題:洛谷P1873
洛谷P2440
洛谷P1024
洛谷P2678

while(l<=r){
	ull mid=(r+l)/2;//l+(r-l)/2或l+r>>1
	if(check(mid)){
		l=mid+1;
	}else{
		r=mid-1;
	}
}
cout<<r;

分數規劃

oi-wiki

簡介:

  • 每種物品有兩個權值 a 和 b,選出若干個物品使得 \(\displaystyle\frac{\sum a}{\sum b}\)最小/最大。

  • 假設我們要求最大值。二分一個答案 mid,然後推式子(為了方便少寫了上下界):

  • \(\displaystyle \begin{aligned} &\frac{\sum a_i\times w_i}{\sum b_i\times w_i}>mid\\ \Longrightarrow&\sum a_i\times w_i-mid\times \sum b_i\cdot w_i>0\\ \Longrightarrow&\sum w_i\times(a_i-mid\times b_i)>0 \end{aligned}\)

  • 那麼只要求出不等號左邊的式子的最大值就行了。如果最大值比 0 要大,說明 mid 是可行的,否則不可行。

  • 求最小值的方法和求最大值的方法類似.


倍增

樹上祖先:

  • f[i][j]為i往上跳\(2^j\)
  • f[i][j]=f[f[i][j-1]][j-1]
  • (↑的意思:跳\(2^j\)步,需要跳\(2^{j-1}+2^{j-1}\)步)

上面就是倍增的思想

LCA

  • 求樹上兩個點最近的公共祖先

  • 暴力一步一跳到自己一級祖先容易超時

  • 可以用倍增思想來跳



作者:d\(i\)en\(t\)er

相關文章