tarjan算法思想:從一個點開始,進行深度優先遍歷,同時記錄到達該點的時間(dfn記錄到達i點的時間),和該點能直接或間接到達的點中的最早的時間(low[i]記錄這個值,其中low的初始值等於dfn)。如圖:
假設我們從1開始DFS,那么到達1的時間為1,到達2的時間為2,到達3的時間為3。同時,點1能直接或間接到達的點中,最小時間為1,點2能通過3間接到達點1,所以點2可到達最早的點時間為1,點3可以直接到達點1,故點3到達的最早的點的時間為1。)。對於每一個沒有被遍歷到的點A,如果從當前點有一條到未遍歷點A的有向邊,則遍歷到A,同時將點A入棧,時間戳+1並用dfn[a]記錄到達點A的時間,枚舉從A發出的每一條邊,如果該邊指向的點沒有被訪問過,那么繼續dfs,回溯后low[a]=min(low[a],low[j])(其中j為A可以到達的點。)如果該點已經訪問過並且該點仍在棧里,那么low[a]=min(low[a],dfn[j])。
解釋:
若點j沒有被訪問過,那么回溯后low[j]就是j能到達最早點,a能到達的最早點當然就是a本身能到達的最早點,或者a通過j間接到達的最早點。若點j已經被訪問過,那么low[j]必然沒有被回溯所更新。所以low[a]就等於a目前能到達的最小點或a直接到達點j時點j的訪問時間。注意:兩個句子中的“或”其實指的是兩者的最小值。
那么如果我們回溯到一個點K他的low[k]=dfn[k]那么我們將K及其以前在棧中的點依次彈出,這些點即為一個強連通分量。(說明從k出發又回到k)
證明:
因為該點dfn=low,所以在棧中的該點以上的點都能由該點直接或間接的到達。同時棧中在該點前的任意一點j,其dfn[j] != low[j](否則點j比點k靠前,又因為dfn[j]=low[j],j一定先被彈出了。)那么這個點j通過low[j]這個時間的點,一定能到達點k,否則,low[j]能到達點i,又因為dfn>=low所以有2種情況1、dfn>low:那么我們可以找到前面一個更小的點。2、dfn=low:應該在回溯到i的時候就找到了一個強連通分量,從而出棧了。而點k前的點沒有出棧,證明其中任意一點都能直接或者間接到達點k,進而證明這些點可以兩兩互達。
先用簡單模板刷一道水題入門:
本題的邊不帶權值,可以用vector表示鄰接鏈表:

#include <cstdio> #include <memory.h> #include <vector> #include <algorithm> using namespace std; //本題的頂點號從1到n,故可直接用作vector的下標,不需要用head數組離散化 const int MAXN = 10001; int top; int Stack[MAXN]; bool inStack[MAXN]; //DFN(u)為節點u搜索的次序編號(時間戳),Low(u)為u或u的子樹能夠追溯到的最早的棧中節點的次序號 int dfn[MAXN], low[MAXN]; int Bcnt, Dindex; //記錄強連通的個數和當前時間 vector<int> ve[MAXN]; //鄰接表保存邊 void init() { Bcnt = Dindex = top = 0; memset(dfn, -1, sizeof dfn); memset(inStack, false, sizeof inStack); for (int i = 1; i < MAXN; ++i) ve[i].clear(); } void tarjan(int u) { int v = 0; dfn[u] = low[u] = ++Dindex; inStack[u] = true; Stack[++top] = u; int t = ve[u].size(); for (int i = 0; i < t; ++i) { v = ve[u][i]; if (dfn[v] == -1) { tarjan(v); low[u] = min(low[u], low[v]); } else if (inStack[v]) low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { Bcnt++; do { v = Stack[top--]; inStack[v] = false; } while (u != v); } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif int n, m; while (scanf("%d%d", &n, &m) != EOF) { if (m == 0 && n == 0) break; init(); while (m--) { int a, b; scanf("%d%d", &a, &b); ve[a].push_back(b); } for (int i = 1; i <= n; ++i) if (dfn[i] == -1) tarjan(i); if (Bcnt == 1) puts("Yes"); else puts("No"); } return 0; }
只需找出所有的強連通分量,然后遍歷所有的邊,對於邊u->v,若u、v在不同的連通子圖,顯然v的子圖可以從邊u->v達到,對於這樣的兩個強連通分量,可以視為一個子圖,只需打電話給u所所在的子圖中人,然后讓這個人再聯系u,v所在的兩個強連通分量中的所有人。故只需找出所有的子圖,打給每個子圖中話費最低的那個人即可。

#include <cstdio> #include <memory.h> #include <algorithm> const int MAXN = 1001; struct N { int u, v;//u to v int next;//下一個頂點 } edge[MAXN * 2]; //m <= 2000 int head[MAXN], edgenum; int DFN[MAXN], Low[MAXN], Dindex;//到達某頂點的費用,該子圖最小費用 ,當前費用 int Bcnt, Belong[MAXN]; //強連通分量的個數, 當前頂點所屬的連通分量 int Stack[MAXN], top; bool inStack[MAXN]; void addedge(int u, int v) { N e = {u, v, head[u]}; edge[edgenum] = e; head[u] = edgenum++; } void init() { edgenum = top = Bcnt = Dindex = 0; memset(head, -1, sizeof head); memset(DFN, -1, sizeof DFN); memset(inStack, false, sizeof inStack); } void tanjar(int u) { int v = 0; DFN[u] = Low[u] = ++Dindex; Stack[++top] = u; inStack[u] = true; for (int i = head[u]; i != -1; i = edge[i].next) { v = edge[i].v; if (DFN[v] == -1) { tanjar(v); Low[u] = std::min(Low[u], Low[v]); } else if (inStack[v]) Low[u] = std::min(Low[u], DFN[v]); } if (DFN[u] == Low[u]) { ++Bcnt; do { v = Stack[top--]; Belong[v] = Bcnt; //將點加入該強連通圖 //printf("bcnt = %d v = %d\n", Bcnt, v); inStack[v] = false; } while (u != v); } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif int n, m; int w[MAXN] = {0}; while (scanf("%d%d", &n, &m) != EOF) { init(); for (int i = 1; i <= n; ++i) scanf("%d", &w[i]); while (m--) { int a, b; scanf("%d%d", &a, &b); addedge(a, b); } for (int i = 1; i <= n; ++i) { if (DFN[i] == -1) tanjar(i); } //合並強連通圖 int mfee[MAXN] = {0};//記錄下每個強連圖集的最小話費 bool inode[MAXN] = {false}; //不可達的連通圖 int pos = 0; for (int i = 0; i < edgenum; ++i) { int u = edge[i].u; int v = edge[i].v; if (Belong[u] != Belong[v]) //v所在的連通子圖可以由u到達 inode[Belong[v]] = true; } for (int i = 1; i <= Bcnt; ++i) { if (!inode[i]) pos++; mfee[i] = 1e9; } for (int i = 1; i <= n; ++i) { int t = Belong[i]; if (!inode[t]) mfee[t] = std::min(mfee[t], w[i]); } int sum = 0; for (int i = 1; i <= Bcnt; ++i) { if (mfee[i] != 1e9) sum += mfee[i]; } printf("%d %d\n", pos, sum); } return 0; }
題意:讓礦車采到盡可能多的礦。#表示牆,數字表示礦的數目,*表示定點傳送,可選擇傳或者不傳,礦車只能向右和向下開,不能倒退。
先來看下SPFA算法,可以求帶環最短(最長路徑):Currency Exchange

#include <cstdio> #include <memory.h> #include <vector> using namespace std; const int MAXN = 100001; inline int Min(int a, int b) { return a < b ? a : b; } inline int Max(int a, int b) { return a > b ? a : b; } struct arc { int u, v, next; } edge[150001]; int head[MAXN]; int edgenum; int DFN[MAXN], Low[MAXN], Dindex; int Stack[MAXN], top; bool inStack[MAXN]; int Belong[MAXN], Bnum[MAXN], Bcnt; void addedge(int u, int v) { arc na = {u, v, head[u]}; edge[edgenum] = na; head[u] = edgenum++; } void init() { Bcnt = top = Dindex = edgenum = 0; memset(head, -1, sizeof head); memset(DFN, 0, sizeof DFN); memset(inStack, false, sizeof inStack); memset(Bnum, 0, sizeof Bnum); } void tarjan(int u) { int v = 0; DFN[u] = Low[u] = ++Dindex; Stack[++top] = u; inStack[u] = true; for (int i = head[u]; i != -1; i = edge[i].next) { v = edge[i].v; if (DFN[v] == 0) { tarjan(v); Low[u] = Min(Low[u], Low[v]); } else if (inStack[v]) Low[u] = Min(Low[u], DFN[v]); } if (DFN[u] == Low[u]) { Bcnt++; do { v = Stack[top--]; inStack[v] = false; Belong[v] = Bcnt; Bnum[Bcnt]++; } while (u != v); } } vector<int> G[MAXN]; bool vis[MAXN]; int dfs(int s) { int maxnum = 0; vis[s] = true; int size = G[s].size(); for (int i = 0; i < size; ++i) { int v = G[s][i]; if (!vis[v]) { int tmp = dfs(v); maxnum = Max(maxnum, tmp); vis[v] = false; } } return Bnum[s] + maxnum; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif int n, m, s; int caseid = 0; while (scanf("%d%d%d", &n, &m, &s) != EOF) { init(); while (m--) { int u, v; scanf("%d%d", &u, &v); addedge(u, v); } tarjan(s); for (int i = 1; i <= Bcnt; ++i) G[i].clear(); for (int i = 0; i < edgenum; ++i) { if (DFN[edge[i].u] == 0) continue; int u = Belong[edge[i].u]; int v = Belong[edge[i].v]; if (u != v) G[u].push_back(v); } memset(vis, false, sizeof vis); printf("Case %d:\n%d\n", ++caseid, dfs(Belong[s])); } return 0; }