1. 程式人生 > 其它 >LiJunLin's Algorithm Notes

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的點為止。

  1. 尋找一入度為0的點並輸出之;
  2. 刪除該點及其所有出邊。

拓撲排序用來確定一個依賴關係集中事物發生的順序。

程式碼

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]++;                   
}

單調佇列

樹狀陣列

線段樹

平衡樹

啟發式合併

樹剖部分

K-Dtree

莫隊演算法

可持久化資料結構

線段樹分治

CDQ分治

動態規劃