寒訓圖論

作者:王政凱、王培軒、林子鈞

內容物

  • 圖介紹
  • BFS/DFS
  • DSU
  • 最小生成樹
  • 最短路徑
  • DAG與拓樸排序
  • LCA

 

  • 建中205林子鈞
  • aka linki,ranger,kumokunn,冷氣
  • 建中資訊社學術長
  • SQCS總召
  • 放課量子講師

講師介紹:

講師介紹:

  • 建中227王培軒
  • PeiGan
  • 建資網管
  • 科班墊底
  • 全國賽拿參加獎
  • 又醜又胖
  • 唱歌難聽
  • 老二很小

什麼是圖?

組成

頂點

有時候會有點權

或是邊權

3

9

4

1

7

3

3

8

2

6

1

9

7

4

基本性質

度數

點的度數:該點連接的邊數

4

2

2

2

1

1

0

路徑

多個相鄰點及其連邊的集合

起點終點相同的路徑

圖的分類

有向圖

3

9

4

1

7

3

3

8

2

6

1

9

7

4

簡單圖

3

9

4

1

7

3

3

8

2

6

1

9

7

4

沒有重邊或自環

完全圖

所有點之間皆有連邊

樹/森林

沒有環的圖稱為森林,每個聯通分量為一樹

二分圖

將點分割為兩個互斥集,且集合內無相鄰點

圖的儲存

鄰接矩陣

二維陣列存圖

n個點:G[n][n]

G[i][j]:從i點連到j點的邊權

\begin{bmatrix} 0&3&0&0&0 \\ 3&7&2&0&8 \\ 0&2&0&6&0 \\ 0&0&6&0&0 \\ 0&8&0&0&0 \end{bmatrix}

2

5

3

1

4

3

8

2

6

7

鄰接串列

vector陣列存圖

n個點:vector G[n]

2

5

3

1

4

3

8

2

6

7

1 : {2,3}

2 : {1,3},{2,7},{3,2},{5,8}

3 : {2,2},{4,6}

4 : {3,6}

5 : {2,8}

圖的遍例

DFS-深度優先搜尋

走到一個點

把所有相鄰點丟進stack

取stack中top繼續走下去

DFS

BFS-廣度優先搜尋

走到一個點

把所有相鄰點丟進queue後端

取queue前端繼續走下去

BFS

題目

DSU

disjoint-set(並查集

有\(n\)個元素,各自屬於某個集合(集合間互斥

希望支援以下兩個操作

  1. 合併兩個集合
  2. 查詢某個元素的集合

實際做法

每個人會有上級,一開始大家的上級都是自己

一個集合裡面的頭頭以自己為上級

查詢時不斷跳到上級直到頭頭

合併時將一個集合的頭頭變成另一個集合頭頭的上級

<我是頭頭

優化

路徑壓縮:找到頭頭後直接把自已的上級設為頭頭

啟發式合併:合併兩集合時將小的和到大的

都使用的複雜度:\(O(\alpha(n)) \)(很快

使用路徑壓縮的code

int dsu[MAXN];
int find(int pos){
	if(dsu[pos]!=pos){
		dsu[pos]=find(dsu[pos]);
		return dsu[pos];
	}else return pos;
}
void unnion(int a,int b){
	dsu[find(a)]=find(b);
}
int main(){
	for(int i=1;i<MAXN;i++)dsu[i]=i;
}

題目

裸的DSU

想一下的DSU

最小生成樹

生成樹

無向連通圖中的子圖
滿足點集和原圖相同

且是一個樹

最小生成樹

所有生成樹中邊權和最小的生成樹

性質:合併兩最小生成樹時

以最小的邊的合併最好

kruskal's algorithm

每次找最小的邊

利用DSU看他們是不是在同一個連通塊

不同就合併

\(O(Elog(V))\)

code

#define mp make_pair
int dsu[MAXN];
int find(int pos){
	if(dsu[pos]!=pos){
		dsu[pos]=find(dsu[pos]);
		return dsu[pos];
	}else return pos;
}
void unnion(int a,int b){
	dsu[find(a)]=find(b);
}
int main(){
	for(int i=1;i<MAXN;i++)dsu[i]=i;
}
int main(){
	priority_queue<pair<int,pair<int,int>>,
    vector<pair<int,pair<int,int>>>,greater<pair<int,pair<int,int>>>> qq;
    int n,m;cin>>n>>m;
    while(m--){
    	int a,b,c;cin>>a>>b>>c;
        qq.push(mp(c,mp(a,b)));
    }
    int ans=0;
    whlie(qq.size()){
    	auto top=qq.top();
        if(find(top.S.S)!=find(top.S.F)){
			unnion(top.S.F,top.S.S);
            ans+=top.F;
		}
    }
    cout<<ans<<endl;
}

Prim's algorithm

選任一個當起點

每次將離當前最小生成樹最近的點

加入最小生成樹

稀疏圖\(O(Elog(V))\)

稠密圖\(O(V^2)\)

code(運送蛋餅

struct qq{
	ll a,b,c;
};
bitset<5001> bi;
ll ve[5001];
qq arr[5001];
ll cost=0;
int main(){
	int n,cnt=0;
	cin>>n;
	for(int i=0;i<n;i++){
		struct qq qq;
		cin>>qq.a>>qq.b>>qq.c;
		arr[i]=qq;
	}
	for(int i=0;i<5001;i++)ve[i]=100000000000;
	int tmp=0;
	while(cnt<n){
		ve[tmp]=0;
		for(int i=0;i<n;i++){
			ve[i]=min(ve[i],(arr[i].a-arr[tmp].a)*(arr[i].a-arr[tmp].a)+(arr[i].b-arr[tmp].b)*(arr[i].b-arr[tmp].b)+(arr[i].c-arr[tmp].c)*(arr[i].c-arr[tmp].c));
		}
		ll min=100000000000,ind;
		for(int i=0;i<n;i++){
			if(ve[i]<min&&ve[i]!=0){
				ind=i;
				min=ve[i];
			}
		}
		cost+=ve[ind];
		tmp=ind;
		cnt++;
	}
	cout<<cost<<endl;
	return 0;
}

題目

最短路徑

DAG與拓撲排序

LCA

寒訓圖論

By linki1010111