LiJunLin's Algorithm Notes
圖論
DFS
#include<iostream> #include<stdio.h> using namespace std; int n,m,t,sx,sy,ex,ey; int a[10][10]; int dir[4][2]={0,1,1,0,-1,0,0,-1}; int ans=0; void dfs(int x,int y){ if(x==ex&&y==ey){ ans++; return; } for(int i=0;i<4;i++){ int nx=x+dir[i][0],ny=y+dir[i][1]; if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]==0){ a[nx][ny]=1; dfs(nx,ny); a[nx][ny]=0; } } } int main(){ cin>>n>>m>>t>>sx>>sy>>ex>>ey; int tx,ty; while(t--){ cin>>tx>>ty; a[tx][ty]=-1;//障礙 } a[sx][sy]=1;//已訪問 dfs(sx,sy); cout<<ans; return 0; }
BFS
BFS求的最短路徑是指步數最少。
//節點結構 struct Node{ int x,y; Node(int x,int y):x(x),y(y){} bool operator = (const Node&A)const{ if(this->x==A.x&&this->y==A.y) return true; return false; } }; //標記陣列 bool visit[MAXW][MAXH]={false}; //記錄路徑 Node FatherNode[MAXW][MAXH]; Stack<Node> S; //方向陣列 int dir[4][2]={0,1,1,0,0,-1,-1,0}; bool BFS(Node &start,Node &end){ queue<Node> Q; Node CurrentNode,AdjacentNode; int i; //將源點放進佇列Q Q.push(Vs); visit[start.x][start.y]=true; while(!Q.empty()){ //取出對頭 CurrentNode=Q.front(); Q.pop(); //判斷相鄰節點是不是end,是就結束函式,不是就推進佇列Q並設定為已訪問 for(i=0;i<4;i++){ AdjacentNode=Node(CurrentNode.x+dir[i][0],CurrentNode.y+dir[i][1]); if(AdjacentNode==end){ FatherNode[AdjacentNode.x][AdjacentNode.y]=CurrentNode; return true; } if(IsValid(AdjacentNode)&&!visit[AdjacentNode.x][AdjacentNode.y]){ Q.push(AdjacentNode); visit[AdjacentNode.x][AdjacentNode.y]=true; FatherNode[AdjacentNode.x][AdjacentNode.y]=CurrentNode; } } } return false; } //回溯FatherNode打印出最短路徑 void Back(){ Node t=FatherNode[4][4]; while(!(t.x==0&&t.y==0)){ S.push(t); t=FatherNode[t.x][t.y]; } }
拓撲排序
AOV網
一個較大的工程往往被劃分成許多子工程,我們把這些子工程稱作活動(activity)。在整個工程中,有些子工程(活動)必須在其它有關子工程完成之後才能開始,也就是說,一個子工程的開始是以它的所有前序子工程的結束為先決條件的,但有些子工程沒有先決條件,可以安排在任何時間開始。為了形象地反映出整個工程中各個子工程(活動)之間的先後關係,可用一個有向圖來表示,圖中的頂點代表活動(子工程),圖中的有向邊代表活動的先後關係,即有向邊的起點的活動是終點活動的前序活動,只有當起點活動完成之後,其終點活動才能進行。通常,我們把這種頂點表示活動、邊表示活動間先後關係的有向圖稱做頂點活動網(Activity On Vertex network),簡稱AOV
AOV是一個DAG。
拓樸排序概念
由某個集合上的一個偏序得到該集合上的一個全序,這個操作稱之為拓撲排序。
拓撲排序求法
由AOV網構造拓撲排序的步驟主要是迴圈執行以下兩步,直到不存在入度為0的點為止。
- 尋找一入度為0的點並輸出之;
- 刪除該點及其所有出邊。
拓撲排序用來確定一個依賴關係集中事物發生的順序。
程式碼:
vector<int> G[MAXV];//鄰接表
int n,m,inDegree[MAXV];//頂點數、入度
//拓撲排序
bool topologicalSort(){
int num=0; //記錄加入拓撲序列的頂點數
queue<int> q;
//將所有入度為0的點壓入q
for(int i=0;i<n;i++){
if(inDegree[i]==0){
q.push(i);
}
}
while(!q.empty()){
int u.q.front();
printf("%d",u);
q.pop();
//將u的鄰接點的入度全減1
for(int i=0;i<G(u).size();i++){
int v=G[u][i];
inDegree[v]--;
if(inDegree[v]==0) q.push(v);
}
//清空u的所有出邊
G[u].clear();
num++;
}
//加入拓撲序列的頂點數為n,說明拓撲排序成功
if(num==n) return true;
return false;
}
差分約束
prufer編碼
最短路徑
SPFA
堆優化Dijkstra
Dijkstra演算法詳解 通俗易懂 - 知乎 (zhihu.com)
最短路徑—弄懂Dijkstra(迪傑斯特拉)演算法 - 雲+社群 - 騰訊雲 (tencent.com)
Dijkstra 演算法是求一個圖中一個點到其他所有點的最短路徑的演算法
Dijkstra是用來求單源最短路徑的
鄰接矩陣版
const int MAXV=1000;//最大頂點數
int n;//頂點數
int G[MAXV][MAXV];
int min_dis[MAXV];//到源點的最短距離
bool vis[MAXV];
void Dijkstra(int s){//s為源點
fill(min_dis,min_dis+MAXV,INF);//初始化min_dis陣列為INF
min_dis[s]=0;//源點到自身最短距離為0
for(int i=0;i<n;i++){//迴圈n次
int u=-1,MIN=INF;
for(int j=0;j<n;j++){//找出未訪問的點中距離最短的點
if(!vis[j]&&d[j]<MIN){
u=j;
MIN=d[j];
}
}
//找不到小於INF的d[u],說明剩下的頂點與源點不通
if(u==-1) return;
vis[u]=true;
//更新以u為中介點的點的距離
for(int v=0;v<n;v++){
//如果v未訪問,且u能到達v,且以u為中介點更優
if(!vis[u]&&G[u][v]!=INF&&d[u]+G[u][v]<d[v]){
d[v]=d[u]+G[u][v];
}
}
}
}
鄰接表+優先佇列版
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<time.h>
#include<sstream>
#include<set>
#include<queue>
#include<limits.h>
using namespace std;
#define ll long long
const int INF=0x3f3f3f;
const double PI=acos(-1.0);
const double EXP=1E-8;
/*------------------------------------------------------------------------*/
const int MAXV=1000;
struct Edge{//為了鄰接表而建
int v,w;//v為邊的目標頂點,w為權重
};
struct Node{//為了優先佇列而建
int u,dis;//u為頂點編號,dis為到源點的距離
bool operator>(const Node&a) const{
return dis>a.dis;
}
};
vector<Edge> Adj[MAXV];//圖G,Adj[u]存放從頂點u的鄰接點
int n,m;//頂點數,邊數
int min_dis[MAXV];
bool vis[MAXV];
int pre[MAXV];//pre[v]表示頂點v的最短路徑上的前一個點
priority_queue<Node,vector<Node>,greater<Node> > q;
void Dijkstra(int s){
fill(min_dis,min_dis+MAXV,INF);
min_dis[s]=0;
Node startnode={s,0};
q.push(startnode);
while(!q.empty()){
int u=q.top().u;
q.pop();
if(vis[u])
continue;
vis[u]=true;
//Update the shortest path from neighbor to source
for(vector<Edge>::iterator ed=Adj[u].begin();ed!=Adj[u].end();ed++){
int v=ed->v,w=ed->w;
if(min_dis[v]>min_dis[u]+w){
min_dis[v]=min_dis[u]+w;
Node nnode={v,min_dis[v]};
q.push(nnode);
pre[v]=u;
}
}
}
}
/*------------------------------------------------------------------------*/
int main(){
clock_t starttime,endtime;
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
/*------------------------------------------------------------------------*/
cin>>n>>m;
/*------------------------------------------------------------------------*/
//starttime=clock();
/*------------------------------------------------------------------------*/
for(int i=0;i<n;i++) pre[i]=i;
for(int i=0;i<m;i++)
{
int x,y,w;
cin>>x>>y>>w;
Edge e={y,w};
Adj[x].push_back(e);
}
Dijkstra(0);
for(int i=n-1;i>=0;i--) cout<<pre[i]<<" ";
/*------------------------------------------------------------------------*/
//endtime=clock();
//cout<<endl<<(double)(endtime-starttime)/CLOCKS_PER_SEC<<"s";
return 0;
}
歐拉回路
Tarjan
匈牙利演算法
KM
Dinic
最小樹形圖
帶花樹
數論
快速冪
\(a^b\)
long long binpow(long long a,long long b){
long long res=1;
while(b>0){
if(b&1) res*=a;
a*=a;
b>>1;
}
return res;
}
\(a^b \% m\)
long long binpow(long long a,long long b,long long m){
long long res =1;
while(b>0){
if(b&1) res=res*a%m;
a=a*a%m;
b>>1;
}
return res%m;
}
費馬小定理
尤拉定理
埃氏篩
時間複雜度O(nlog(logn))
isprime[0]=isprime[1]=false;
for(int i=2;i<=maxn;i++){
if(isprime[i]){
prime[cnt++]=i;
for(int j=i*i;j<=maxn;j+=i){
isprime[j]=false;
}
}
}
這裡有一個小優化,j 從 i * i 而不是從 i + i開始,因為 i*(2~ i-1)在 2~i-1時都已經被篩去,所以從i * i開始。
對於一個合數,有可能被篩多次。例如 30 = 2 * 15 = 3 * 10 = 5*6……
用篩法求之N內的素數。 - C語言網 (dotcpp.com)
尤拉篩
for(int i=2;i<=maxn;i++){
if(IsPrime[i]){
prime[++prime[0]]=i;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=maxn;j++){
IsPrime[i*prime[j]]=false;
if(i%prime[j]==0)
break;
}
}
尤拉篩保證了每個合數只被它的最小質因子篩除。
\(i_0 \% prime[j]==0\)保證了這一特性,理由如下,
\(當i_0=k*prime[j]時,i_0*prime[j+1]=prime[j]*k*prime[j+1]\),
\(這說明後面當遇到一個i_1=k*prime[j+1]時,會篩除i_1*prime[j]=i_0*prime[j+1]\)。
前面的不會篩除後面會篩除的。
例子:求n以內的素數
#include<iostream>
#include<bitset>
using namespace std;
int prime[10000];
bitset<10000> IsPrime;
int main()
{
int n,i,j;
cin >> n;
IsPrime[0] = IsPrime[1] = 1;
for(i=2;i<n;i++){
if(!IsPrime[i]){
prime[++prime[0]] = i;
printf("%d\n", i);
}
for (j = 1; j <= prime[0] && i * prime[j] < n; j++) {
IsPrime[i * prime[j]] = 1;
if (i % prime[j] == 0)
break;
}
}
return 0;
}
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<sstream>
#include<time.h>
using namespace std;
/*---------------------------------------------------------------*/
#define maxn 20210605
bool IsPrime[maxn];
int prime[maxn];
void oula(){
memset(IsPrime,true,sizeof(IsPrime));
IsPrime[0]=IsPrime[1]=false;
for(int i=2;i<=maxn;i++){
if(IsPrime[i]) prime[++prime[0]]=i;
for(int j=1;j<=prime[0]&&i*prime[j]<=maxn;j++){
IsPrime[i*prime[j]]=false;
if(i%prime[j]==0) break;
}
}
}
bool judge(int x){
if(!IsPrime[x]) return false;
while(x!=0){
if(!IsPrime[x%10]) return false;
x/=10;
}
return true;
}
int ans;
/*---------------------------------------------------------------*/
int main(){
clock_t starttime,endtime;
starttime=clock();
/*---------------------------------------------------------------*/
oula();
for(int i=1;i<=maxn;i++){
if(judge(i)){
printf("%d ",i);
ans++;
}
}
printf("%d",ans);
/*---------------------------------------------------------------*/
endtime=clock();
printf("\n耗時:%f s\n",(double)(endtime-starttime)/CLOCKS_PER_SEC);
return 0;
}
線性篩
輾轉相除法
歐幾里得演算法
gcd(a,b)=gcd(b,a mod b);
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
擴充套件歐幾里得
#include<stdio.h>
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1;y=0;
return a;
}
int d=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-(a/b)*y;
return d;
}
int main(){
int x,y;
exgcd(2,3,x,y);
printf("%d %d",x,y);
return 0;
}
中國剩餘定理
容斥原理
Lucas定理
BSGS演算法
線性基
高斯消元
解二次剩餘
拉格朗日插值
莫比烏斯反演
求原根
NTT
快速傅立葉變換&快速數論變換
杜教篩
min_25篩
Burnside定理
Polya定理
類歐幾里得
字串
KMP
擴充套件KMP
字串HASH
Manacher
Trie樹
AC自動機及Trie圖
字尾陣列SA
字尾自動機SAM
迴文樹
計算幾何
點積
叉積
向量代數運算
凸包的計算
旋轉卡殼
半平面交
K次圓覆蓋問題
辛普森積分
模擬退火
資料結構
連結串列
雜湊表
ST表
堆
並查集
演算法學習筆記(1) : 並查集 - 知乎 (zhihu.com)
初始化
int fa[MAXN];
inline void init(int n)
{
for (int i = 1; i <= n; ++i)
fa[i] = i;//父節點為自己
}
查詢
一層一層訪問父節點,直至根節點(根節點的標誌就是父節點是本身)。要判斷兩個元素是否屬於同一個集合,只需要看它們的根節點是否相同即可。
int find(int x)
{
if(fa[x] == x)//父節點為自身,為根節點
return x;
else
return find(fa[x]);
}
合併
inline void merge(int i, int j)
{
fa[find(i)] = find(j);//把i的父節點的父節點設定為j的父節點
}
合併(路徑壓縮)
int find(int x)
{
if(x == fa[x])
return x;
else{
fa[x] = find(fa[x]); //父節點設為父節點的父節點,最後這條路上所有點父節點都設為根節點
return fa[x]; //返回父節點
}
}
以上程式碼常常簡寫為一行:
int find(int x)
{
return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
初始化(按秩合併)
inline void init(int n)
{
for (int i = 1; i <= n; ++i)
{
fa[i] = i;
rank[i] = 1;
}
}
合併(按秩合併)
inline void merge(int i, int j)
{
int x = find(i), y = find(j); //先找到兩個根節點
if (rank[x] <= rank[y])//x的高度小於等於y,則合併x那一路到y
fa[x] = y;
else
fa[y] = x;
if (rank[x] == rank[y] && x != y)//如果高度相同且根節點不同,則新的根節點的高度+1
rank[y]++;
}