1. 程式人生 > 其它 >Tarjan 連通性

Tarjan 連通性

Tarjan 連通性

Tarjan 爺爺的代表作,圖的連通性問題直接解決

兩個核心陣列:

  1. \(dfn_u\)\(u\) 的 dfs 序
  2. \(low_u\)\(u\)\(u\) 的後代通過返祖邊能回到的最小的 \(dfn\)

四種邊

  • 樹邊:dfs 搜尋樹中的邊
  • 返祖邊:若在搜尋樹中, \(i\)\(j\) 的祖先,則原圖中從 \(j\)\(i\) 的邊是返祖邊
  • 前向邊:若在搜尋樹中, \(i\)\(j\) 的後代,則原圖中從 \(j\)\(i\) 的邊是前向邊
  • 交叉邊:若在搜尋樹中, \(i\)\(j\) 不在同一子樹,則從 \(i\)\(j\)
    的邊是交叉邊

前向邊、交叉邊只存在於有向圖

核心程式碼

int dfn[N],low[N],clk;
void tarjan(int u,int fa) {
    dfn[u]=low[u]=++clk;
    for(int i=lst[u],v;i;i=nxt[i]) {
        if(!dfn[v=to[i]]) {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
        } else if(這是返祖邊)
            low[u]=min(low[u],dfn[v]);
    }
}
int main() {
    // Input ....
    for(int i=1;i<=n;i++)
        if(!dfn[i])tarjan(i,-1);
}

無向圖

無向圖的操作與連通分量有關

  • 連通分量:無向圖的極大連通子圖

割點

  • 定義:刪去一個點使得圖中聯通分量增加,該點叫做割點

  • 求法:如果 \(u\) 存在一個兒子 \(v\)\(low_v\ge dfn_u\)

    說明刪去 \(u\)\(v\) 不能通過返祖邊連回 \(u\) 的祖先, \(u\) 是割點

  • 特判:如果 \(u\) 是搜尋樹的根且只有 1 個兒子,則 \(u\) 不是割點

割邊(橋)

  • 定義:刪去一條邊使得圖中聯通分量增加,該點叫做割邊(橋)

  • 求法:如果對於一條邊 \((u,v)\)\(low_v>dfn_u\)

    說明刪去該邊後 \(v\)

    不能通過返祖邊連回 \(u\)\(u\) 的祖先,該邊是割邊(橋)

割點 & 橋 的實現

int cut[N],br[M<<1];
void tarjan(int u,int fa) {
    dfn[u]=low[u]=++clk;
    register int ch=0;
    for(int i=lst[u],v;i;i=nxt[i]) {
        if(!dfn[v=to[i]]) {
			++ch;
			tarjan(v,u);
			if(low[v]>=dfn[u])cut[u]=1;
			if(low[v]>dfn[u])br[i]=br[i^1]=1;
			low[u]=min(low[u],low[v]);
        } else if(v^fa)
            low[u]=min(low[u],dfn[v]);
    }
    if(fa==-1 && ch==1)cut[u]=0;
}

點雙連通分量

  • 定義:無向圖的極大無割點子圖 ,簡稱點雙

  • 求法:一個點(如割點)可能在多個點雙中,而一條邊只能在一個點雙中

    考慮用棧存下邊,當 \(u\) 是割點時,一直彈棧直到彈出邊 \((u,v)\),彈出的邊組成點雙

vector<int>bs[N];
int id[N],tot;
void tarjan(int u,int fa) {
    dfn[u]=low[u]=++clk;
    for(int i=lst[u],v,nw;i;i=nxt[i]) {
        if(!dfn[v=to[i]]) {
            s[++top]=i;
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]) {
                tot++;
                while(1) {
                    nw=s[top--];
                    if(id[to[nw]]!=tot)
                        id[to[nw]]=tot,bs[tot].pb(to[nw]);
                    if(id[to[nw^1]]!=tot)
                        id[to[nw^1]]=tot,bs[tot].pb(to[nw^1]);
                    if(to[nw]==v && to[nw^1]==u)break;
                }
            }
        } else if(v!=fa && dfn[v]<dfn[u]) {
            low[u]=min(low[u],dfn[v]);
            s[++top]=i;
        }
    }
}

邊雙連通分量

  • 定義:無向圖的極大無橋子圖,簡稱邊雙
  • 求法:可用點雙的思想,把點壓棧,然後遇到橋彈點到彈出 \(u\) ,彈出的點組成邊雙
  • 求法 2:更簡單。第一次 \(dfs\) 求出橋,第二次 \(dfs\) 打標記,遇到橋就編號加 1 ,編號相同的點構成邊雙
void tarjan(int u,int fa) {
    dfn[u]=low[u]=++clk;
    for(int i=lst[u],v;i;i=nxt[i]) {
        if(!dfn[v=to[i]]) {
			++ch;
			tarjan(v,u);
			if(low[v]>dfn[u])br[i]=br[i^1]=1;
			low[u]=min(low[u],low[v]);
        } else if(v^fa)
            low[u]=min(low[u],dfn[v]);
    }
}
int tot;
void dfs(int u,int fa,int nw) {
    cl[u]=nw;
    for(int i=lst[u];i;i=nxt[i])
        if((v=to[i])^fa) {
            if(br[i])++tot,dfs(v,u,tot);
            else dfs(v,u,nw);
        }
}

有向圖

強連通分量

有向圖中 tarjan 用於求強連通分量

  • 強連通圖:一個任意兩點都可以相互到達的有向圖
  • 強連通分量:一個有向圖的極大的強連通子圖

求法:將點入棧,遇到 \(dfn_u=low_u\) 時就彈棧頂直到彈出 \(u\) ,彈出的點在一個強連通分量

\(low_u<dfn_u\) 時說明強連通分量還可以擴大,所以只能 \(dfn_u=low_u\)

int sz[N],cl[N],tot;
void tarjan(int u) {
    dfn[u]=low[u]=++clk,s[++top]=u;
    for(int i=lst1[u],v;i;i=nxt1[i]) {
        if(!dfn[v=to1[i]]) {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        } else if(!cl[v]) {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(low[u]==dfn[u]) {
        ++tot,sz[tot]=1;
        while(s[top]!=u)
            ++sz[tot],cl[s[top]]=tot,--top;
        cl[u]=tot,--top;
    }
}

縮點

  • 作用:將強連通分量看成一個點,可以將原圖縮成一個 DAG

    可以用於方便地做 dp

  • 做法:求強連通分量給點染色,然後列舉一個每條邊 \((u,v)\)

    如果 \(color_u\ne color_v\) 就從 \(color_u\)\(color_v\) 連邊

for(int i=1;i<=n;i++)
	for(int j=lst[i];j;j=nxt[j])
		if(cl[i]!=cl[to[j]])
			Ae2(cl[i],cl[to[j]]);

[HAOI2010]軟體安裝

縮點後一定是樹,在 \(\text{dfs}\) 序上 dp

\(f_{i, j}\) 為做到了 \(dfs\) 序為 \(i\) 的點用了空間為 \(j\) 的最大價值

\(\text{dfs}\) 序為 \(i\) 的點是 \(u\)\(w\) 是所用空間,\(v\) 是價值

刷表,對於 \(i\) 選或不選討論

\(f_{i+1,j+w_u} = \max(f_{i,j}+v_i)\)

\(f_{i+sz_u,j}=\max(f_{i,j})\)

處理依賴:從根到該點路徑上(不包括該店)的 \(w\) 之和為必須要的空間

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 105;
int n, m, a[N], Ecnt1, lst1[N], b[N], clk;
int dfn[N], st[N], top, cl[N], low[N], tot;
int w[N], v[N], lst2[N], Ecnt2;
int rk[N], sz[N], pre[N], rd[N], f[N][505];
struct Ed { int to, nxt; } e1[N], e2[N];
inline void cmx(int &x, int y) { x < y ? x = y : 1; }
inline void Ae1(int fr, int go) { e1[++Ecnt1] = (Ed){ go, lst1[fr] }, lst1[fr] = Ecnt1; }
inline void Ae2(int fr, int go) { e2[++Ecnt2] = (Ed){ go, lst2[fr] }, lst2[fr] = Ecnt2; }
void tarjan(int u) {
    dfn[u] = low[u] = ++clk, st[++top] = u;
    for (int i = lst1[u], v; i; i = e1[i].nxt) {
        if (!dfn[v = e1[i].to]) {
            tarjan(v), low[u] = min(low[u], low[v]);
        } else if (!cl[v]) low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) {
        register int o;
        ++tot;
        while (st[top] ^ u)
            o = st[top--], cl[o] = tot, w[tot] += a[o], v[tot] += b[o];
        cl[u] = tot, w[tot] += a[u], v[tot] += b[u], --top;
    }
}
void dfs(int u) {
    rk[++clk] = u, sz[u] = 1;
    for (int i = lst2[u], v; i; i = e2[i].nxt)
        pre[v = e2[i].to] = pre[u] + w[u], dfs(v), sz[u] += sz[v];
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
    for (int i = 1, u; i <= n; i++) {
        scanf("%d", &u);
        if (u) Ae1(u, i);
    }
    for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
    for (int i = 1; i <= n; i++)
        for (int j = lst1[i], v; j; j = e1[j].nxt)
            if (cl[i] ^ cl[v = e1[j].to]) ++rd[cl[v]], Ae2(cl[i], cl[v]);
    for (int i = 1; i <= tot; i++) if (!rd[i]) Ae2(0, i);
    clk = 0, dfs(0);
    for (int i = 1, u; i <= clk; i++) {
        u = rk[i];
        for (int j = pre[u]; j <= m; j++) {
            if (j + w[u] <= m) cmx(f[i + 1][j + w[u]], f[i][j] + v[u]);
            cmx(f[i + sz[u]][j], f[i][j]);
        }
    }
    printf("%d", f[clk + 1][m]);
}