圖論2

最短路徑、最小生成樹

接下來\(V\) 代表點數、\(E\) 代表邊數

\((u,v)\)代表由\(u\)通往\(v\)的邊

單點源最短路徑

鬆弛(Relaxation)

假設除了起點,到別的點的距離都是 \(\infty\)

 

若邊\((u,v)\)之權重為\(w\)

則更新\(dis[v] =min(dis[v],dis[u] + w)\)

Bellman-Ford演算法

可以發現,我們每次做鬆弛,其實就是模擬走一條邊,所以我們只要好好模擬這個過程就可以了。

複雜度: \(O(VE)\)

DP想法!!

優化:

會發現其實並不需要鬆弛那麼多次,只要在知道\(dis[u]\)是最小的情況下,直接拿這個點對其他點做鬆弛就好了。

怎麼知道\(dis[u]\)最小了呢?

經過鬆弛後的結果一定比\(dis[u]\)大(無負邊情況下)

故只要\(u\)是所有未知的點中距離最小的,\(dis[u]\)一定已是最小的,固可拿來幫其他的做鬆弛。

Dijkstra's Algorithm

用pq維護還沒鬆弛過的點

取距離最小者來鬆其他點直到全部都被鬆過為止

複雜度:\(O((V+E)logE)\)

int dis[MAXN];
vector<pair<int,int>> graph[MAXN];
void dijkstra_algorithm(int start,int end){
	priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
	bool visited[n+1] = {false};
	for(int i=1;i<=n;i++) dis[i] = INF;
	dis[start] = 0;
	pq.push({0,start});
	while(pq.size()){
		auto [w,v] = pq.top();
		pq.pop();
		if(visited[v]) continue;
		visited[v] = 1;
		for(auto [w_2,v_2]:graph[v]){
			if(w+w_2<dis[v_2]){//relaxation
				dis[v_2] = w+w_2;
				pq.push({dis[v_2],v_2});
			}
		}
	}
}

dijkstra不能處理負邊!

題目:

次短路徑

多點源最短路徑

Floyd-Warshall Algorithm

bj4 跟Bellman-Ford證明很像

可以自己找(我懶)

int dis[MAXN][MAXN];
void Floyd_Warshall(int start,int end){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			for(int k=1;k<=n;k++){
				dis[i][j] = min(dis[i][j],dis[i][k]+dis[k][j]);
			}
		}
	}
}

\(dis[i][j]\)要先預處理好!

複雜度:\(O(n^3)\)

題目:

MST

最小生成樹

生成樹?

定義:

對於一張圖,只取裡面的幾條邊,組成一棵樹,並讓邊權之和最小。

明顯的觀念:

1.只要連了\(V-1\)條邊後MST就做好了(樹的性質)

2.要讓邊權和最小,取邊權最小的邊比較好(廢話)

Prim's algorithm

跟前面dijkstra's有87%相似

隨便選一個起點,每次向外找邊權最小的邊加進MST,再繼續上述步驟直到\(V-1\)條邊為止。

複雜度:\(O((V+E)logE)\)

Other solutions?

10

5

兩條紅線 你要選哪個來連接兩個MST?

Kruskal's Algorithm

做法:

照邊權排序

取邊權最小的邊

若兩個點屬於不同MST,就透過該邊合併

重複直到\(V-1\)條邊

 

第三步怎麼做到?

並查集

Disjoin-Set Union(DSU)

合併Union:將兩個集合合併

查詢Query:查詢一個點處在的集合

TODO:

聽不懂? 用幫派來解釋!

合併Union:將兩個幫派合併成一個

查詢Query:檢查這個人是哪個幫派的

合併Union:把其中一個人的首領變另個的手下

查詢Query:看兩個人幫派的首領一不一樣

路徑壓縮:最後都是只查首領,所以改成直接紀錄首領是誰,遞迴時沿路更改。\(O(N)->O(1)\)

const int MAXN = (int)2e5;
int dsu[MAXN]; //第i項表示i點的父節點
void Init(){
    for (int i=0;i<MAXN;i++) dsu[i] = i;
}
//先將每項的父元素設為本身
int query(int k){
    if (k==dsu[k]) return k;
    dsu[k] = query(dsu[k]);//老大的老大就是我的老大
    return dsu[k];
}
void modify (int x,int y){
    int xA = query(x);
    int yA = query(y);
    if (xA!=yA) dsu[xA] = yA;
}

簡單的DSU題目練習:CF445B

Back to Kruskal!

\(O(ElogE)\)

先試著利用kruskal的概念自己寫寫看演算法吧!


struct road{
    ll f;
    ll s;
    ll val;
};
int dsu[MAXN];
vector<road> graph;
int find(int x){
    return dsu[x] == x ? x : dsu[x] = find(dsu[x]);
}
void modify(int x,int y){
    int pa = find(x);
    int pb = find(y);
    if(pa!=pb) dsu[pb] = pa;
}
void solve(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) dsu[i] = i;
    while(m--){
        road r;
        cin>>r.f>>r.s>>r.val;
        graph.pb(r);
    }
    ll sum = 0;
    sort(all(graph),[](road a,road b){return a.val<b.val;});
    for(auto [a,b,c]:graph){
        if(find(a)!=find(b)){
            sum+=c;
            modify(a,b);
        }
    }
    cout<<sum<<endl;
    return;
}

題目:

提示:

邊權為0,1的圖,如果能做出權重\(a,b(a<b)\)的生成樹,則可以做出\([a,b]\)權重的生成樹。

Made with Slides.com